├── .DS_Store ├── .ipynb_checkpoints └── test-checkpoint.ipynb ├── 151. 翻转字符串里的单词.md ├── 264. 丑数 II.md ├── README.md ├── README.pdf ├── python ├── 1.两数之和.md ├── 10. 正则表达式匹配.md ├── 100. 相同的树.md ├── 101.对称二叉树.md ├── 102. 二叉树的层次遍历.md ├── 103. 二叉树的锯齿形层次遍历.md ├── 104. 二叉树的最大深度.md ├── 1047. 删除字符串中的所有相邻重复项.md ├── 105. 从前序与中序遍历序列构造二叉树.md ├── 106. 从中序与后序遍历序列构造二叉树.md ├── 108. 将有序数组转换为二叉搜索树.md ├── 11. 盛最多水的容器.md ├── 110. 平衡二叉树.md ├── 1103. 分糖果 II.md ├── 111. 二叉树的最小深度.md ├── 112. 路径总和.md ├── 113. 路径总和 II.md ├── 114. 二叉树展开为链表.md ├── 118. 杨辉三角.md ├── 121. 买卖股票的最佳时机.md ├── 122.买卖股票的最佳时机II.md ├── 123. 买卖股票的最佳时机 III.md ├── 124. 二叉树中的最大路径和.md ├── 125. 验证回文串.md ├── 128. 最长连续序列.md ├── 13. 罗马数字转整数.md ├── 131. 分割回文串.md ├── 136. 只出现一次的数字.md ├── 137. 只出现一次的数字 II.md ├── 139. 单词拆分.md ├── 14.最长公共前缀.md ├── 141.环形链表.md ├── 142. 环形链表 II.md ├── 144. 二叉树的前序遍历.md ├── 145. 二叉树的后序遍历.md ├── 146. LRU缓存机制.md ├── 148. 排序链表.md ├── 15.三数之和.md ├── 152. 乘积最大子序列.md ├── 153. 寻找旋转排序数组中的最小值.md ├── 154. 寻找旋转排序数组中的最小值 II.md ├── 155. 最小栈.md ├── 16. 最接近的三数之和.md ├── 160. 相交链表.md ├── 167. 两数之和 II - 输入有序数组.md ├── 169.求众数.md ├── 17. 电话号码的字母组合.md ├── 171. Excel表列序号.md ├── 172. 阶乘后的零.md ├── 179. 最大数.md ├── 189. 旋转数组.md ├── 19. 删除链表的倒数第N个节点.md ├── 190. 颠倒二进制位.md ├── 191. 位1的个数.md ├── 198. 打家劫舍.md ├── 2.两数相加.md ├── 20.有效的括号.md ├── 200. 岛屿数量.md ├── 201. 数字范围按位与.md ├── 202. 快乐数.md ├── 204. 计数质数.md ├── 206. 反转链表.md ├── 207. 课程表.md ├── 208.前缀树.md ├── 21.合并两个有序链表.md ├── 215. 数组中的第K个最大元素.md ├── 216. 组合总和 III.md ├── 217. 存在重复元素.md ├── 219. 存在重复元素 II.md ├── 22. 括号生成.md ├── 220. 存在重复元素 III.md ├── 221. 最大正方形.md ├── 225. 用队列实现栈.md ├── 226.翻转二叉树.md ├── 227. 基本计算器 II.md ├── 229.求众数II.md ├── 23. 合并K个排序链表.md ├── 230.二叉搜索树中第k小的元素.md ├── 231. 2的幂.md ├── 232. 用栈实现队列.md ├── 234. 回文链表.md ├── 235. 二叉搜索树的最近公共祖先.md ├── 236. 二叉树的最近公共祖先.md ├── 237.删除链表中的结点.md ├── 238. 除自身以外数组的乘积.md ├── 239. 滑动窗口最大值.md ├── 240. 搜索二维矩阵II.md ├── 242. 有效的字母异位词.md ├── 26. 删除排序数组中的重复项.md ├── 260. 只出现一次的数字 III.md ├── 263. 丑数.md ├── 268. 缺失数字.md ├── 279. 完全平方数.md ├── 28. 实现 strStr().md ├── 283. 移动零.md ├── 287. 寻找重复数.md ├── 289.生命游戏.md ├── 29. 两数相除.md ├── 292. Nim 游戏.md ├── 295. 数据流的中位数.md ├── 3.无重复字符的最长子串.md ├── 300. 最长上升子序列.md ├── 309.最佳买卖股票时机含冷冻期.md ├── 31. 下一个排列.md ├── 312. 戳气球.md ├── 315. 计算右侧小于当前元素的个数.md ├── 32. 最长有效括号.md ├── 322. 零钱兑换.md ├── 326. 3的幂.md ├── 328. 奇偶链表.md ├── 33. 搜索旋转排序数组.md ├── 334. 递增的三元子序列.md ├── 337. 打家劫舍 III.md ├── 338. 比特位计数.md ├── 34. 在排序数组中查找元素的第一个和最后一个位置.md ├── 343. 整数拆分.md ├── 344. 反转字符串.md ├── 347. 前 K 个高频元素.md ├── 350. 两个数组的交集 II.md ├── 36. 有效的数独.md ├── 371. 两整数之和.md ├── 378. 有序矩阵中第K小的元素.md ├── 384. 打乱数组.md ├── 387. 字符串中的第一个唯一字符.md ├── 39..组合总和.md ├── 392. 判断子序列.md ├── 394. 字符串解码.md ├── 395. 至少有K个重复字符的最长子串.md ├── 399. 除法求值.md ├── 4. 寻找两个有序数组的中位数.md ├── 40. 组合总和 II.md ├── 401. 二进制手表.md ├── 406. 根据身高重建队列.md ├── 410. 分割数组的最大值.md ├── 412. Fizz Buzz.md ├── 414. 第三大的数.md ├── 416. 分割等和子集.md ├── 42. 接雨水.md ├── 43. 字符串相乘.md ├── 437. 路径总和 III.md ├── 438. 找到字符串中所有字母异位词.md ├── 448. 找到所有数组中消失的数字.md ├── 45. 跳跃游戏 II.md ├── 46. 全排列.md ├── 461. 汉明距离.md ├── 47.全排列II.md ├── 477. 汉明距离总和.md ├── 48. 旋转图像.md ├── 49. 字母异位词分组.md ├── 494. 目标和.md ├── 496. 下一个更大元素 I.md ├── 5. 最长回文子串.md ├── 50. Pow(x, n).md ├── 503. 下一个更大元素 II.md ├── 509. 斐波那契数.md ├── 523. 连续的子数组和.md ├── 53. 最大子序和.md ├── 538. 把二叉搜索树转换为累加树.md ├── 54. 螺旋矩阵.md ├── 543. 二叉树的直径.md ├── 55. 跳跃游戏.md ├── 557. 反转字符串中的单词 III.md ├── 559. N叉树的最大深度.md ├── 56.合并区间.md ├── 560. 和为K的子数组.md ├── 581. 最短无序连续子数组.md ├── 59. 螺旋矩阵 II.md ├── 617. 合并二叉树.md ├── 62. 不同路径.md ├── 63. 不同路径 II.md ├── 64. 最小路径和.md ├── 647. 回文子串.md ├── 654. 最大二叉树.md ├── 66. 加一.md ├── 668. 乘法表中第k小的数.md ├── 69. x 的平方根.md ├── 7. 整数反转.md ├── 70.爬楼梯.md ├── 704. 二分查找.md ├── 714. 买卖股票的最佳时机含手续费.md ├── 72. 编辑距离.md ├── 739. 每日温度.md ├── 74. 搜索二维矩阵.md ├── 746. 使用最小花费爬楼梯.md ├── 75. 颜色分类.md ├── 76. 最小覆盖子串.md ├── 77. 组合.md ├── 78.子集.md ├── 79. 单词搜索.md ├── 8. 字符串转换整数 (atoi).md ├── 84. 柱状图中最大的矩形.md ├── 85. 最大矩形.md ├── 876. 链表的中间结点.md ├── 88. 合并两个有序数组.md ├── 9. 回文数.md ├── 90. 子集 II.md ├── 92. 反转链表 II.md ├── 94.二叉树的中序遍历.md ├── 96. 不同的二叉搜索树.md ├── 98. 验证二叉搜索树.md └── 994. 腐烂的橘子.md ├── test.ipynb ├── 剑指offer ├── 面试题04. 二维数组中的查找.md ├── 面试题05. 替换空格.md ├── 面试题06. 从尾到头打印链表.md ├── 面试题07. 重建二叉树.md ├── 面试题09. 用两个栈实现队列.md ├── 面试题10- I. 斐波那契数列.md ├── 面试题10- II. 青蛙跳台阶问题.md ├── 面试题11. 旋转数组的最小数字.md ├── 面试题12. 矩阵中的路径.md ├── 面试题13. 机器人的运动范围.md ├── 面试题14- I. 剪绳子.md ├── 面试题14- II. 剪绳子 II.md ├── 面试题15. 二进制中1的个数.md ├── 面试题16. 数值的整数次方.md ├── 面试题17. 打印从1到最大的n位数.md ├── 面试题18. 删除链表的节点.md ├── 面试题21. 调整数组顺序使奇数位于偶数前面.md ├── 面试题22. 链表中倒数第k个节点.md ├── 面试题24. 反转链表.md ├── 面试题26. 树的子结构.md ├── 面试题27. 二叉树的镜像.md ├── 面试题28. 对称的二叉树.md ├── 面试题29. 顺时针打印矩阵.md ├── 面试题30. 包含min函数的栈.md ├── 面试题32 - I. 从上到下打印二叉树.md ├── 面试题32 - II. 从上到下打印二叉树 II.md ├── 面试题32 - III. 从上到下打印二叉树 III.md ├── 面试题34. 二叉树中和为某一值的路径.md ├── 面试题39. 数组中出现次数超过一半的数字.md ├── 面试题3:数组中重复的数字.md ├── 面试题47. 礼物的最大价值.md ├── 面试题48. 最长不含重复字符的子字符串.md ├── 面试题50. 第一个只出现一次的字符.md ├── 面试题52. 两个链表的第一个公共节点.md ├── 面试题55 - I. 二叉树的深度.md ├── 面试题55 - II. 平衡二叉树.md ├── 面试题57 - II. 和为s的连续正数序列.md ├── 面试题57. 和为s的两个数字.md ├── 面试题58 - I. 翻转单词顺序.md ├── 面试题58 - II. 左旋转字符串.md ├── 面试题64. 求1+2+…+n.md ├── 面试题65. 不用加减乘除做加法.md ├── 面试题68 - II. 二叉树的最近公共祖先.md └── 题目记录.md └── 面试题49. 丑数.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linxid/Leetcode_Python/bfbb5d4ded43e104f621f02e7559ddc721876e09/.DS_Store -------------------------------------------------------------------------------- /.ipynb_checkpoints/test-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 2 6 | } 7 | -------------------------------------------------------------------------------- /151. 翻转字符串里的单词.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个字符串,逐个翻转字符串中的每个单词。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: "the sky is blue" 8 | 输出: "blue is sky the" 9 | 示例 2: 10 | 11 | 输入: "  hello world!  " 12 | 输出: "world! hello" 13 | 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 14 | ``` 15 | 16 | ### 解析1: 17 | 拆分,倒序,拼接,全都是python的split的强大。 18 | 19 | ```python 20 | class Solution: 21 | def reverseWords(self, s: str) -> str: 22 | return ' '.join(s.split()[::-1]) 23 | ``` 24 | 25 | ### 解析2: 26 | 遍历添加法,去掉两边的空格,然后从尾部开始倒着添加。 27 | 28 | ```python 29 | class Solution: 30 | def reverseWords(self, s: str) -> str: 31 | s = s.strip() 32 | res = "" 33 | i, j = len(s) - 1, len(s) 34 | while i > 0: 35 | if s[i] == ' ': 36 | res += s[i + 1: j] + ' ' 37 | while s[i] == ' ': i -= 1 38 | j = i + 1 39 | i -= 1 40 | return res + s[:j] 41 | ``` -------------------------------------------------------------------------------- /264. 丑数 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 编写一个程序,找出第 n 个丑数。 3 | 4 | 丑数就是只包含质因数 2, 3, 5 的正整数。 5 | 6 | 示例: 7 | 8 | 输入: n = 10 9 | 输出: 12 10 | 解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。 11 | 说明:   12 | 13 | 1 是丑数。 14 | n 不超过1690。 15 | 16 | ### 解析1: 17 | 动态规划。 18 | 19 | **算法流程:** 20 | 重点是DP的状态转移方程:`dp[i] = min(2*dp[p2], 3*dp[p3], 5*dp[p5])` 21 | p2,p3,p5分别是三个指针,代表最靠前,最新的没有被乘以2,3,5的数值的指针。这里有点拗口。我们一次求丑数的时候,新的丑数肯定是以前的某个数或者某几个数乘以2,3,5得到的。如果最后一个数,大于前面的数乘以2,3,5,那么将对应的指针加1。因为n很小,所以复杂度可以认为是O(1)。 22 | 23 | 三个指针代表什么呢,代表,当前指针后面的数都不是当前值乘以2/3/5得到的。比如p2,代表已知p2后面的数,都不是p2值乘以2得到的。 24 | 25 | **复杂度:** 26 | | |复杂度|大小|百分比| 27 | |--|--|--|--| 28 | |时间|$O(1)$|240 ms|43.20%| 29 | |空间|$O(n)$|13.7 MB|6.09%| 30 | 31 | ```python 32 | class Solution: 33 | def nthUglyNumber(self, n: int) -> int: 34 | if n == 1:return 1 35 | res = [0]*n 36 | p2 = p3 = p5 = 0 37 | res[0] = 1 38 | for i in range(1, n): 39 | res[i] = min(2*res[p2], 3*res[p3], 5*res[p5]) 40 | if res[i] >= 2 * res[p2]: 41 | p2 += 1 42 | if res[i] >= 3 * res[p3]: 43 | p3 += 1 44 | if res[i] >= 5 * res[p5]: 45 | p5 += 1 46 | return res[-1] 47 | ``` -------------------------------------------------------------------------------- /README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linxid/Leetcode_Python/bfbb5d4ded43e104f621f02e7559ddc721876e09/README.pdf -------------------------------------------------------------------------------- /python/10. 正则表达式匹配.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。 3 | 4 | '.' 匹配任意单个字符 5 | '*' 匹配零个或多个前面的那一个元素 6 | 所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。 7 | 8 | 说明: 9 | 10 | s 可能为空,且只包含从 a-z 的小写字母。 11 | p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。 12 | 示例 1: 13 | ``` 14 | 输入: 15 | s = "aa" 16 | p = "a" 17 | 输出: false 18 | 解释: "a" 无法匹配 "aa" 整个字符串。 19 | ``` 20 | 21 | ### 解析1: 22 | 不会做,依次匹配,匹配'.'和‘*’。 23 | ```python 24 | class Solution(object): 25 | def isMatch(self, text, pattern): 26 | if not pattern: 27 | return not text 28 | 29 | first_match = bool(text) and pattern[0] in {text[0], '.'} 30 | 31 | if len(pattern) >= 2 and pattern[1] == '*': 32 | return (self.isMatch(text, pattern[2:]) or 33 | first_match and self.isMatch(text[1:], pattern)) 34 | else: 35 | return first_match and self.isMatch(text[1:], pattern[1:]) 36 | ``` -------------------------------------------------------------------------------- /python/100. 相同的树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定两个二叉树,编写一个函数来检验它们是否相同。 3 | 4 | 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 5 | ``` 6 | 示例 1: 7 | 8 | 输入: 1 1 9 | / \ / \ 10 | 2 3 2 3 11 | 12 | [1,2,3], [1,2,3] 13 | 14 | 输出: true 15 | ``` 16 | 17 | ### 解析1: 18 | 递归判断,先判断根节点是否相同,然后递归的判断左右子树是否相同。主要是根节点的判断。1.根节点是否都为空;2.是否一个为空。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|24 ms|60.56%| 23 | |空间|$O(1)$|11.9 MB|21.12%| 24 | 25 | ```python 26 | class Solution(object): 27 | def isSameTree(self, p, q): 28 | if p == None and q == None: 29 | return True 30 | if p == None or q == None: 31 | return False 32 | if p.val != q.val: 33 | return False 34 | return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) 35 | ``` -------------------------------------------------------------------------------- /python/101.对称二叉树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,检查它是否是镜像对称的。 3 | ``` 4 | 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 5 | 6 | 1 7 | / \ 8 | 2 2 9 | / \ / \ 10 | 3 4 4 3 11 | 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 12 | 13 | 1 14 | / \ 15 | 2 2 16 | \ \ 17 | 3 3 18 | ``` 19 | 20 | ### 解析1: 21 | **设置dfs辅助函数,比较左子树结点和右子树结点是否是相等的,以及左右子树是否对称。**--再判断左子树的右结点和右子树的左结点是否是相等的,左子树的左结点和右子树的右结点是否是相等的。 22 | 23 | * **算法流程:** 24 | 1. 设置辅助函数,来判断两棵树是否相等。如果两棵树均为空返回True; 25 | 2. 如果两棵树一个为空,一个不为空,则返回False; 26 | 3. 判断两棵树结点值是否相等、左树的左结点和右树的右结点是否相等、左树的右结点和右树的左结点是否相等。 27 | 28 | * **复杂度:** 29 | | |复杂度|大小|百分比| 30 | |--|--|--|--| 31 | |时间|$O(n)$|40 ms|12.15%| 32 | |空间|$O(n)$|12.1 MB|10.14%| 33 | 34 | ```python 35 | class Solution(object): 36 | def isSymmetric(self, root): 37 | def dfs(left,right): 38 | if not left and not right:return True 39 | if not left or not right:return False 40 | 41 | return left.val == right.val and \ 42 | dfs(left.left, right.right) and \ 43 | dfs(left.right, right.left) 44 | return dfs(root, root) 45 | ``` -------------------------------------------------------------------------------- /python/103. 二叉树的锯齿形层次遍历.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 3 | ``` 4 | 例如: 5 | 给定二叉树 [3,9,20,null,null,15,7], 6 | 7 | 3 8 | / \ 9 | 9 20 10 | / \ 11 | 15 7 12 | 返回锯齿形层次遍历如下: 13 | 14 | [ 15 | [3], 16 | [20,9], 17 | [15,7] 18 | ] 19 | ``` 20 | 21 | ### 解析1: 22 | 和上一题层次遍历差不多,只是加一个层的判断,奇数和偶数层的处理不同,增加一个判断即可。 23 | 24 | * **复杂度:** 25 | | |复杂度|大小|百分比| 26 | |--|--|--|--| 27 | |时间|$O(n)$|40 ms|97.71%| 28 | |空间|$O(n)$|13.8 MB|5.96%| 29 | 30 | ```python 31 | class Solution: 32 | def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]: 33 | if not root:return None 34 | res,queue = [],[root] 35 | layer = 0 36 | while queue: 37 | temp = [] 38 | for _ in range(len(queue)): 39 | node = queue.pop(0) 40 | temp.append(node.val) 41 | if node.right:queue.append(node.right) 42 | if node.left:queue.append(node.left) 43 | 44 | if layer%2 == 0:res.append(temp[::-1]) 45 | else:res.append(temp) 46 | layer += 1 47 | return res 48 | ``` -------------------------------------------------------------------------------- /python/104. 二叉树的最大深度.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,找出其最大深度。 3 | 4 | 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 5 | 6 | 说明: 叶子节点是指没有子节点的节点。 7 | 8 | ``` 9 | 示例: 10 | 给定二叉树 [3,9,20,null,null,15,7], 11 | 12 | 3 13 | / \ 14 | 9 20 15 | / \ 16 | 15 7 17 | 返回它的最大深度 3 。 18 | ``` 19 | 20 | ### 解析1: 21 | 递归遍历每一个结点,比较左右子树的深度大小,返回左右子树较大值+1. 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(n)$|44 ms|80.45%| 26 | |空间|$O(logn)$|15 MB|24.81%| 27 | 28 | 每一次递归调用都需要保存到栈里面,正常二叉树的层数Logn即可。 29 | 30 | ```python 31 | class Solution: 32 | def maxDepth(self, root: TreeNode) -> int: 33 | if not root: 34 | return 0 35 | 36 | left = self.maxDepth(root.left) 37 | right = self.maxDepth(root.right) 38 | return left + 1 if left>right else right + 1 39 | 40 | # 优化后的代码 41 | class Solution: 42 | def maxDepth(self, root: TreeNode) -> int: 43 | if not root:return 0 44 | return max(self.maxDepth(root.left),self.maxDepth(root.right))+1 45 | ``` 46 | 47 | ### 解析2: 48 | 迭代实现,遍历每一个结点。 49 | 50 | | |复杂度|大小|百分比| 51 | |--|--|--|--| 52 | |时间|$O(n)$|68 ms|51.04%| 53 | |空间|$O(logn)$|15 MB|86.14%| 54 | 55 | ```python 56 | class Solution: 57 | def maxDepth(self, root): 58 | stack = [] 59 | if root is not None: 60 | stack.append((1, root)) 61 | 62 | depth = 0 63 | while stack != []: 64 | current_depth, root = stack.pop() 65 | if root is not None: 66 | depth = max(depth, current_depth) 67 | stack.append((current_depth + 1, root.left)) 68 | stack.append((current_depth + 1, root.right)) 69 | 70 | return depth 71 | ``` -------------------------------------------------------------------------------- /python/1047. 删除字符串中的所有相邻重复项.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。 3 | 4 | 在 S 上反复执行重复项删除操作,直到无法继续删除。 5 | 6 | 在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。 7 | 8 | ``` 9 | 示例: 10 | 11 | 输入:"abbaca" 12 | 输出:"ca" 13 | 解释: 14 | 例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。 15 | ``` 16 | 17 | ### 解析1: 18 | 栈依次保存每一个字符,如果新元素和栈顶元素相同,那么pop,否则继续添加。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|80 ms|93.76%| 23 | |空间|$O(1)$|14 MB |100%| 24 | 25 | ```python 26 | class Solution: 27 | def removeDuplicates(self, S: str) -> str: 28 | stack = [] 29 | for c in S: 30 | if not stack or stack[-1] != c: 31 | stack.append(c) 32 | else: 33 | stack.pop() 34 | return ''.join(stack) 35 | ``` -------------------------------------------------------------------------------- /python/105. 从前序与中序遍历序列构造二叉树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 根据一棵树的前序遍历与中序遍历构造二叉树。 3 | 4 | 注意: 5 | 你可以假设树中没有重复的元素。 6 | ``` 7 | 例如,给出 8 | 9 | 前序遍历 preorder = [3,9,20,15,7] 10 | 中序遍历 inorder = [9,3,15,20,7] 11 | 返回如下的二叉树: 12 | 13 | 3 14 | / \ 15 | 9 20 16 | / \ 17 | 15 7 18 | ``` 19 | ### 解析1: 20 | 求根结点,然后得到左子树的前序遍历和中序遍历,对左子树递归调用。求右子树的前序遍历和中序遍历,右子树递归调用。 21 | 22 | * **算法流程:** 23 | 1. 如果前序遍历或中序遍历为空返回None; 24 | 2. 前序遍历的第一个元素是根节点,求根节点; 25 | 3. 获取根节点在中序遍历的索引,即根节点在中序遍历中的位置; 26 | 4. 左子树的前序遍历:preorder[1:1+index],中序遍历:inorder[:index] 27 | 5. 右子树的前序遍历:preorder[1+index:],中序遍历:inorder[index+1:] 28 | 29 | * **复杂度:** 30 | | |复杂度|大小|百分比| 31 | |--|--|--|--| 32 | |时间|$O(n)$|184 ms|83.18%| 33 | |空间|$O(n)$|86.2 MB|26.87%| 34 | 35 | 36 | ```python 37 | class Solution(object): 38 | def buildTree(self, preorder, inorder): 39 | if not preorder or not inorder:return None 40 | val = preorder[0] 41 | root = TreeNode(val) 42 | index = inorder.index(val) 43 | root.left = self.buildTree(preorder[1:1+index], inorder[:index]) 44 | root.right = self.buildTree(preorder[1+index:], inorder[index+1:]) 45 | return root 46 | ``` -------------------------------------------------------------------------------- /python/106. 从中序与后序遍历序列构造二叉树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 根据一棵树的中序遍历与后序遍历构造二叉树。 3 | 4 | 注意: 5 | 你可以假设树中没有重复的元素。 6 | 7 | 例如,给出 8 | ``` 9 | 中序遍历 inorder = [9,3,15,20,7] 10 | 后序遍历 postorder = [9,15,7,20,3] 11 | 返回如下的二叉树: 12 | 13 | 3 14 | / \ 15 | 9 20 16 | / \ 17 | 15 7 18 | ``` 19 | 20 | ### 解析1: 21 | 和第105题一样,获取根节点,以及划分好左右子树。 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(n)$|220 ms|40.65%| 26 | |空间|$O(n)$|86.2 MB|30.30%| 27 | 28 | 29 | ```python 30 | class Solution(object): 31 | def buildTree(self, inorder, postorder): 32 | if not inorder or not postorder:return None 33 | val = postorder[-1] 34 | # print(val, inorder, postorder) 35 | index = inorder.index(val) 36 | root = TreeNode(val) 37 | 38 | root.left = self.buildTree(inorder[:index], postorder[:index]) 39 | root.right = self.buildTree(inorder[index+1:],postorder[index:-1]) 40 | return root 41 | ``` -------------------------------------------------------------------------------- /python/108. 将有序数组转换为二叉搜索树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 3 | 4 | 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 5 | ``` 6 | 示例: 7 | 8 | 给定有序数组: [-10,-3,0,5,9], 9 | 10 | 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 11 | 12 | 0 13 | / \ 14 | -3 9 15 | / / 16 | -10 5 17 | ``` 18 | 19 | ### 解析1: 20 | 取每一个数组的中位数作为根节点,然后对前半部分遍历,作为左子树,右半部分遍历作为右子树。 21 | 22 | | |复杂度|大小|百分比| 23 | |--|--|--|--| 24 | |时间|$O(n)$|92 ms|78.67%| 25 | |空间|$O(n)$|16.1 MB|5.59%| 26 | 27 | ```python 28 | class Solution: 29 | def sortedArrayToBST(self, nums: List[int]) -> TreeNode: 30 | if not nums:return None 31 | mid = (len(nums)-1)//2 32 | res = TreeNode(nums[mid]) 33 | 34 | res.left = self.sortedArrayToBST(nums[:mid]) 35 | res.right = self.sortedArrayToBST(nums[mid+1:]) 36 | return res 37 | ``` -------------------------------------------------------------------------------- /python/11. 盛最多水的容器.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 3 | 4 | ### 解析1: 5 | 遍历两两元素组的集合,求每个间隔的大小。 6 | 7 | | |复杂度|大小|百分比| 8 | |--|--|--|--| 9 | |时间|$O(n^2)$|超时| | 10 | |空间|$O(1)$| | | 11 | 12 | ```python 13 | class Solution(object): 14 | def maxArea(self, height): 15 | res = 0 16 | length = len(height) 17 | for i in range(length-1): 18 | for j in range(i+1, length): 19 | temp = (j-i)*min(height[i], height[j]) 20 | res = max(res, temp) 21 | return res 22 | ``` 23 | 24 | 25 | ### 解析2: 26 | 双指针,设置两个指针分别从两端开始向里边凑。主要是左右指针移动的提交。对于左右两个边界,小的移动。 27 | 28 | | |复杂度|大小|百分比| 29 | |--|--|--|--| 30 | |时间|$O(n)$|136 ms|65.41%| 31 | |空间|$O(1)$|13.1 MB |28.08% | 32 | 33 | 步骤: 34 | 1. 设置双指针,分别从0和len(height)-1开始遍历; 35 | 2. 比较面积的大小; 36 | 3. 如果左指针边界高度小于右指针高度:左指针移动,反之右指针移动。总之谁小谁移动。 37 | 38 | ```python 39 | class Solution: 40 | def maxArea(self, height): 41 | """ 42 | :type height: List[int] 43 | :rtype: int 44 | """ 45 | max_area,left,right = 0,0,len(height)-1 46 | while(left < right): 47 | max_area = max(max_area, (right-left)*min(height[left], height[right])) 48 | if height[left] < height[right]:left += 1 49 | else:right -= 1 50 | return max_area 51 | ``` -------------------------------------------------------------------------------- /python/1103. 分糖果 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 排排坐,分糖果。 3 | 4 | 我们买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友。 5 | 6 | 给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后一个小朋友 n 颗糖果。 7 | 8 | 然后,我们再回到队伍的起点,给第一个小朋友 n + 1 颗糖果,第二个小朋友 n + 2 颗,依此类推,直到给最后一个小朋友 2 * n 颗糖果。 9 | 10 | 重复上述过程(每次都比上一次多给出一颗糖果,当到达队伍终点后再次从队伍起点开始),直到我们分完所有的糖果。注意,就算我们手中的剩下糖果数不够(不比前一次发出的糖果多),这些糖果也会全部发给当前的小朋友。 11 | 12 | 返回一个长度为 num_people、元素之和为 candies 的数组,以表示糖果的最终分发情况(即 ans[i] 表示第 i 个小朋友分到的糖果数)。 13 | ``` 14 | 示例 1: 15 | 16 | 输入:candies = 7, num_people = 4 17 | 输出:[1,2,3,1] 18 | 解释: 19 | 第一次,ans[0] += 1,数组变为 [1,0,0,0]。 20 | 第二次,ans[1] += 2,数组变为 [1,2,0,0]。 21 | 第三次,ans[2] += 3,数组变为 [1,2,3,0]。 22 | 第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]。 23 | ``` 24 | 25 | ### 解析1: 26 | 遍历每一个位置,更新索引,更新新加入的值。初始化数组为0。 27 | 28 | * **复杂度:** 29 | | |复杂度|大小|百分比| 30 | |--|--|--|--| 31 | |时间|$O(max(G,N))$|72 ms|12.90%| 32 | |空间|$O(1)$|13.6 MB|23.65%| 33 | 这个复杂度牛逼。 34 | 35 | ```python 36 | class Solution: 37 | def distributeCandies(self, candies: int, num_people: int) -> List[int]: 38 | res = [0]*num_people 39 | temp = 1 40 | idx = 0 41 | while candies: 42 | if idx>=num_people:idx %= num_people 43 | if candies < temp: 44 | res[idx] += candies 45 | break 46 | res[idx] += temp 47 | candies -= temp 48 | idx += 1 49 | temp += 1 50 | return res 51 | ``` 52 | 53 | 思路一样,代码进行了优化。看起来更加的简介,复杂度并没有降低。 54 | 55 | ```python 56 | class Solution: 57 | def distributeCandies(self, candies: int, num_people: int) -> List[int]: 58 | ans = [0] * num_people 59 | i = 0 60 | while candies != 0: 61 | ans[i % num_people] += min(i + 1, candies) 62 | candies -= min(i + 1, candies) 63 | i += 1 64 | return ans 65 | ``` -------------------------------------------------------------------------------- /python/111. 二叉树的最小深度.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,找出其最小深度。 3 | 4 | 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 5 | 6 | 说明: 叶子节点是指没有子节点的节点。 7 | ``` 8 | 示例: 9 | 10 | 给定二叉树 [3,9,20,null,null,15,7], 11 | 12 | 3 13 | / \ 14 | 9 20 15 | / \ 16 | 15 7 17 | 返回它的最小深度  2 18 | ``` 19 | 20 | ### 解析1: 21 | 22 | * **算法流程:** 23 | 使用队列,BFS迭代遍历,如果左右子树都不存在,返回层数,即为最小深度。 24 | 25 | * **算法流程:** 26 | | |复杂度|大小|百分比| 27 | |--|--|--|--| 28 | |时间|$O(n)$|68 ms|21%| 29 | |空间|$O(logn)$|14.7 MB|45.14%| 30 | 31 | ```python 32 | class Solution: 33 | def minDepth(self, root: TreeNode) -> int: 34 | if not root:return 0 35 | res = 0 36 | queue = [root] 37 | while queue: 38 | res += 1 39 | for _ in range(len(queue)): 40 | node = queue.pop(0) 41 | if not node.left and not node.right:return res 42 | if node.left:queue.append(node.left) 43 | if node.right:queue.append(node.right) 44 | return res 45 | ``` 46 | 47 | ### 解析2: 48 | 递归法求解。 49 | * **算法流程:** 50 | * 1.如果root为空,返回0; 51 | * 2.如果root.left和root.right都为空,返回1; 52 | * 3.如果左子树不为空,求左子树的最小深度然后+1;如果右子树不为空,。。同样逻辑。 53 | 54 | * **算法流程:** 55 | | |复杂度|大小|百分比| 56 | |--|--|--|--| 57 | |时间|$O(n)$|68 ms|21%| 58 | |空间|$O(logn)$|14.7 MB|45.14%| 59 | 60 | ```python 61 | class Solution: 62 | def minDepth(self, root: TreeNode) -> int: 63 | if not root:return 0 64 | if not root.left and not root.right:return 1 65 | res = float('inf') 66 | if root.left:res = min(res, self.minDepth(root.left)) 67 | if root.right:res = min(res, self.minDepth(root.right)) 68 | return res + 1 69 | ``` -------------------------------------------------------------------------------- /python/114. 二叉树展开为链表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,原地将它展开为链表。 3 | 4 | ``` 5 | 例如,给定二叉树 6 | 7 | 1 8 | / \ 9 | 2 5 10 | / \ \ 11 | 3 4 6 12 | 将其展开为: 13 | 14 | 1 15 | \ 16 | 2 17 | \ 18 | 3 19 | \ 20 | 4 21 | \ 22 | 5 23 | \ 24 | 6 25 | ``` 26 | 27 | ### 解析1: 28 | 前序遍历,但是要原地修改,所以会比较麻烦。 29 | 30 | | |复杂度|大小|百分比| 31 | |--|--|--|--| 32 | |时间|$O(n)$|56 ms|72.62%| 33 | |空间|$O(n)$|13.8 MB|5.71%| 34 | 35 | 1. 修改当前结点的左右子树,right指向左子树,left指向None; 36 | 2. 对所有结点进行递归操作,保存root.right值 37 | ```python 38 | class Solution: 39 | def __init__(self): 40 | self.pre = None 41 | def flatten(self, root: TreeNode) -> None: 42 | if not root: return 43 | # self.pre保存上一层的结点,更新上一层结点的左子树和右子树; 44 | 45 | if self.pre: self.pre.right, self.pre.left = root, None 46 | self.pre = root 47 | right = root.right 48 | self.flatten(root.left) 49 | self.flatten(right) 50 | ``` -------------------------------------------------------------------------------- /python/118. 杨辉三角.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。 3 | 4 | ``` 5 | 示例: 6 | 7 | 输入: 5 8 | 输出: 9 | [ 10 | [1], 11 | [1,1], 12 | [1,2,1], 13 | [1,3,3,1], 14 | [1,4,6,4,1] 15 | ] 16 | ``` 17 | 18 | ### 解析1: 19 | 遍历上一层,然后构建下一层的结果即可。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n^2)$|40 ms|95.12%| 24 | |空间|$O(n)$|13.8 MB|5.99%| 25 | 26 | ```python 27 | class Solution: 28 | def generate(self, numRows: int) -> List[List[int]]: 29 | if numRows == 0:return [] 30 | res = [[1]] 31 | for i in range(1,numRows): 32 | temp = [1] 33 | last = res[-1] 34 | for i in range(len(last)-1): 35 | temp.append(last[i]+last[i+1]) 36 | temp.append(1) 37 | res.append(temp) 38 | return res 39 | ``` -------------------------------------------------------------------------------- /python/123. 买卖股票的最佳时机 III.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 3 | 4 | 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 5 | 6 | 注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 7 | 8 | >示例 1: 9 | 输入: [3,3,5,0,0,3,1,4] 10 | 输出: 6 11 | 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 12 |   随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。 13 | 14 | ### 解析1: 15 | 限制只能两次交易,所以两次的状态转移方程分开建立。 16 | 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(n)$|68 ms|97.84%| 20 | |空间|$O(1)$|12.6MB|35.22%| 21 | 22 | 23 | ```python 24 | class Solution: 25 | def maxProfit(self, prices): 26 | if not prices: 27 | return 0 28 | 29 | len_prices = len(prices) 30 | buy1, sell1, buy2, sell2 = -prices[0], 0, -prices[0], 0 31 | 32 | for i in range(1, len_prices): 33 | buy1 = max(buy1, -prices[i]) 34 | sell1 = max(sell1, buy1 + prices[i]) 35 | buy2 = max(buy2, sell1 - prices[i]) 36 | sell2 = max(sell2, buy2 + prices[i]) 37 | return sell2 38 | ``` -------------------------------------------------------------------------------- /python/124. 二叉树中的最大路径和.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非空二叉树,返回其最大路径和。 3 | 4 | 本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。 5 | ``` 6 | 示例 1: 7 | 8 | 输入: [1,2,3] 9 | 10 | 1 11 | / \ 12 | 2 3 13 | 14 | 输出: 6 15 | ``` 16 | 17 | ### 解析1: 18 | 递归求解,建立一个函数,自底向上,求解以当前节点作为根节点的最大路径和。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|108 ms|96.77%| 23 | |空间|$O(logn)$|22.3 MB|5.00%| 24 | 25 | ```python 26 | class Solution: 27 | def maxPathSum(self, root: TreeNode) -> int: 28 | self.res = float('-inf') 29 | def helper(root): 30 | if not root:return 0 31 | # 求解以左结点为根结点的最大路径和 32 | left = helper(root.left) 33 | # 以右结点为根结点的路径和 34 | right = helper(root.right) 35 | 36 | # 更新全局变量 37 | self.res = max(left + right + root.val,self.res) 38 | # 当前节点到叶结点,单条路径的最大路径和 39 | return max(0, max(left, right)+root.val) 40 | 41 | helper(root) 42 | return self.res 43 | ``` -------------------------------------------------------------------------------- /python/125. 验证回文串.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 3 | 4 | 说明:本题中,我们将空字符串定义为有效的回文串。 5 | ``` 6 | 示例 1: 7 | 8 | 输入: "A man, a plan, a canal: Panama" 9 | 输出: true 10 | 示例 2: 11 | 12 | 输入: "race a car" 13 | 输出: false 14 | ``` 15 | 16 | ### 解析1: 17 | 做一下筛选,然后再判断是否相等。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(n)$|52 ms|97.88%| 22 | |空间|$O(n)$|14.9 MB|12.56%| 23 | 24 | ```python 25 | class Solution: 26 | def isPalindrome(self, s: str) -> bool: 27 | s = s.lower() 28 | s = [x for x in s if x.isalpha() or x.isdigit()] 29 | s = ''.join(s) 30 | 31 | return s == s[::-1] 32 | ``` -------------------------------------------------------------------------------- /python/128. 最长连续序列.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个未排序的整数数组,找出最长连续序列的长度。 3 | 4 | 要求算法的时间复杂度为 O(n)。 5 | ``` 6 | 示例: 7 | 8 | 输入: [100, 4, 200, 1, 3, 2] 9 | 输出: 4 10 | 解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。 11 | ``` 12 | 13 | ### 解析1: 14 | 去重,然后排序,求最长连续序列长度,判断是否相邻。 15 | 16 | | |复杂度|大小|百分比| 17 | |--|--|--|--| 18 | |时间|$O(nlogn)$|72 ms|82.70%| 19 | |空间|$O(1)$|15 MB|5.09%| 20 | 21 | ```python 22 | class Solution: 23 | def longestConsecutive(self, nums: List[int]) -> int: 24 | nums = sorted(set(nums)) 25 | 26 | n = len(nums) 27 | if n < 2: 28 | return n 29 | res = 1 30 | temp = 1 31 | for i in range(1,n): 32 | 33 | if nums[i] - nums[i-1] <= 1: 34 | temp += 1 35 | res = max(res, temp) 36 | else: 37 | temp = 1 38 | return res 39 | ``` 40 | 41 | ### 解析2: 42 | 将数保存在set中,遍历set,如果num-1不在set中,以num为连续序列的开始,做遍历。对于连续序列中间的数,因为num-1在set中,所以不会开始后面的循环。 43 | 44 | | |复杂度|大小|百分比| 45 | |--|--|--|--| 46 | |时间|$O(n)$|72 ms|82.70%| 47 | |空间|$O(1)$|15 MB|5.09%| 48 | 49 | 步骤: 50 | 1. 对nums取set,遍历数组(set); 51 | 1. 如果num-1不在set中,则启动后面的循环,将其作为连续序列的开始,求连续序列的长度; 52 | 2. 求连续序列的最大长度,求结果; 53 | 54 | ```python 55 | class Solution: 56 | def longestConsecutive(self, nums: List[int]) -> int: 57 | nums = set(nums) 58 | res = 0 59 | for num in nums: 60 | if num-1 not in nums: 61 | end_num = num + 1 62 | while end_num in nums: 63 | end_num += 1 64 | res = max(res, end_num-num) 65 | return res 66 | ``` -------------------------------------------------------------------------------- /python/13. 罗马数字转整数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 3 | 4 | 字符 数值 5 | I 1 6 | V 5 7 | X 10 8 | L 50 9 | C 100 10 | D 500 11 | M 1000 12 | 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做  XXVII, 即为 XX + V + II 。 13 | 14 | 通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况: 15 | 16 | I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。 17 | X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。  18 | C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。 19 | 给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。 20 | ``` 21 | 示例 1: 22 | 23 | 输入: "III" 24 | 输出: 3 25 | 示例 2: 26 | 27 | 输入: "IV" 28 | 输出: 4 29 | ``` 30 | 31 | ### 解析1: 32 | 主要是发现一个规律,如果一个较小的数出现另一个前面,则减去这个数,其他依次保存相加即可。 33 | 34 | | |复杂度|大小|百分比| 35 | |--|--|--|--| 36 | |时间|$O(n)$|60 ms|96.68%| 37 | |空间|$O(logn)$|13.9 MB|5.25%| 38 | 39 | ```python 40 | class Solution: 41 | def romanToInt(self, s: str) -> int: 42 | d = {'I':1, 'IV':3, 'V':5, 'IX':8, 'X':10, 'XL':30, 'L':50, 'XC':80, 'C':100, 'CD':300, 'D':500, 'CM':800, 'M':1000} 43 | 44 | return sum([-d[s[i]] if d[s[i]] < d[s[i+1]] else d[s[i]] for i in range(len(s)-1)] + [d[s[-1]]]) 45 | ``` -------------------------------------------------------------------------------- /python/131. 分割回文串.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 3 | 给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。 4 | 5 | 返回 s 所有可能的分割方案。 6 | ``` 7 | 示例: 8 | 9 | 输入: "aab" 10 | 输出: 11 | [ 12 | ["aa","b"], 13 | ["a","a","b"] 14 | ] 15 | ``` 16 | 17 | ### 解析1: 18 | 回溯法,注意回溯模板,主要还是利用模板,这种题目很套路。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|96 ms|94.33%| 23 | |空间|$O(n)$|13.8 MB|5.05%| 24 | 25 | ```python 26 | class Solution: 27 | def partition(self, s: str) -> List[List[str]]: 28 | res = [] 29 | def helper(s, temp): 30 | if s == '': 31 | res.append(temp) 32 | else: 33 | for i in range(1,len(s)+1): 34 | if s[:i] == s[:i][::-1]: 35 | helper(s[i:], temp + [s[:i]]) 36 | helper(s, []) 37 | return res 38 | ``` -------------------------------------------------------------------------------- /python/137. 只出现一次的数字 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。 3 | 4 | 说明: 5 | 6 | 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 7 | ``` 8 | 示例 1: 9 | 10 | 输入: [2,2,3,2] 11 | 输出: 3 12 | ``` 13 | 14 | ### 解析1: 15 | 依然是统计次数,然后判断,用烂的套路。 16 | 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(n)$|56 ms|83.72%| 20 | |空间|$O(n)$|14.6 MB|5.45%| 21 | 22 | 23 | ```python 24 | class Solution(object): 25 | def singleNumber(self, nums): 26 | cnt_dict = {} 27 | for num in nums: 28 | cnt_dict[num] = cnt_dict.get(num, 0) + 1 29 | for key,value in cnt_dict.items(): 30 | if value == 1: 31 | return key 32 | return -1 33 | ``` 34 | 35 | ### 解析2: 36 | 数学公式:$3(a+b+c) - (3×a + 3×b + c) = 2×c$ 37 | 38 | ```python 39 | class Solution(object): 40 | def singleNumber(self, nums): 41 | return (3*sum(set(nums)) - sum(nums))//2 42 | ``` 43 | 44 | ### 解析3: 45 | 依然是位运算,好吧还是您牛逼,认输。 46 | 47 | | |复杂度|大小|百分比| 48 | |--|--|--|--| 49 | |时间|$O(n)$|48ms|98%| 50 | |空间|$O(n)$|13MB|40.45%| 51 | 52 | ```python 53 | class Solution: 54 | def singleNumber(self, nums): 55 | ones, twos = 0, 0 56 | for num in nums: 57 | ones = ones ^ num & ~twos 58 | twos = twos ^ num & ~ones 59 | return ones 60 | ``` -------------------------------------------------------------------------------- /python/139. 单词拆分.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 3 | 4 | 说明: 5 | 6 | 拆分时可以重复使用字典中的单词。 7 | 你可以假设字典中没有重复的单词。 8 | ``` 9 | 示例 1: 10 | 11 | 输入: s = "leetcode", wordDict = ["leet", "code"] 12 | 输出: true 13 | 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。 14 | ``` 15 | 16 | ### 解析1: 17 | 动态规划,确定状态含义和转移方程即可。 18 | 状态含义:dp[i] 表示前i个词是否可以由wordDict组成; 19 | 转移方程:dp[i] = if dp[j] and s[j:i] in wordDict for j in range(0,i) 20 | 遍历0-i个位置,判断s[j:i]是否在wordDict,同时判断dp[j]是否为真。 21 | 22 | 23 | ```python 24 | class Solution: 25 | def wordBreak(self, s: str, wordDict: List[str]) -> bool: 26 | if not wordDict:return False 27 | n = len(s) 28 | dp = [False for _ in range(n+1)] 29 | dp[0] = True 30 | wordDict = set(wordDict) 31 | for i in range(1,n+1): 32 | for j in range(i-1, -1,-1): 33 | if dp[j] and s[j:i] in wordDict: 34 | dp[i] = True 35 | return dp[n] 36 | ``` 37 | -------------------------------------------------------------------------------- /python/14.最长公共前缀.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 编写一个函数来查找字符串数组中的最长公共前缀。 3 | 4 | 如果不存在公共前缀,返回空字符串 ""。 5 | 6 | >示例 1: 7 | 输入: ["flower","flow","flight"] 8 | 输出: "fl" 9 | 10 | ### 解析1:垂直比较 11 | 每次都取各个字符串的同一列字符,如果完全相同那么set后长度为1。 12 | 求第一个不是0的位置,前面都是1,则代表都是相同的。 13 | 14 | 时间:最坏O(m*n),n个长度为m完全一样的字符串;正常情况O(n * minLen) 15 | 空间:O(m),为字符串的最大长度。 16 | 17 | ```python 18 | class Solution(object): 19 | def longestCommonPrefix(self, strs): 20 | # 将 str 中所有字符串并列到迭代器中,逐次并列返回 str 中所有字符串的第 1、2、3、…… 个字符 21 | num0 = [len(set(c))==1 for c in zip(*strs)] + [0] 22 | # index 搜索第一个 0 的位置 23 | return strs[0][:num0.index(0)] if strs else '' 24 | ``` 25 | 26 | ### 解析2:水平比较 27 | 每个字符串两两比较,比如,第0和第1找最小公共前缀s1,再把s1和第2个比较,后面依次比较。 28 | 29 | 步骤: 30 | 1. 第一个字符作为最长公共前缀prefix; 31 | 2. prefix和后面字符依次比较,更新prefix; 32 | 1. 判断prefix和字符的最长公共前缀,从头比较每一个字符,直到不相等位置,如果相等一直+1, 33 | 2. 注意边界条件,主要是边界的处理; 34 | 35 | 时间:最坏O(m*n),n个长度为m完全一样的字符串 36 | 空间:O(1) 37 | 38 | ```python 39 | class Solution(object): 40 | def longestCommonPrefix(self, strs): 41 | if strs == []: 42 | return '' 43 | 44 | prefix = strs[0] 45 | for i in range(1, len(strs)): 46 | length = min(len(prefix), len(strs[i])) 47 | j = 0 48 | while j < length: 49 | if prefix[j] != strs[i][j]: 50 | break 51 | j += 1 52 | prefix = prefix[:j] 53 | return prefix 54 | ``` -------------------------------------------------------------------------------- /python/141.环形链表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个链表,判断链表中是否有环。 3 | 4 | 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 5 | 6 | 7 | 示例 1: 8 | 9 | 输入:head = [3,2,0,-4], pos = 1 10 | 输出:true 11 | 解释:链表中有一个环,其尾部连接到第二个节点。 12 | 13 | ### 解析1: 14 | 快慢指针。设置快慢指针,如果两个再次重合则存在环。 15 | 16 | | |复杂度|大小|百分比| 17 | |--|--|--|--| 18 | |时间|$O(n)$|32 ms|99.61%| 19 | |空间|$O(n)$|18.3 MB|5.65%| 20 | 21 | 步骤: 22 | 1. 设置快慢指针均从从head开始; 23 | 2. 循环标志,fast和fast.next不为空,如果出现fast和slow再次相等则为环形。 24 | 25 | ```python 26 | class Solution(object): 27 | def hasCycle(self, head): 28 | fast = head 29 | slow = head 30 | while fast and fast.next: 31 | fast = fast.next.next 32 | slow = slow.next 33 | if fast == slow: 34 | return True 35 | return False 36 | ``` 37 | 38 | ### 解析2: 39 | 遍历链表,如果不是奇葩值便修改成奇葩值,如果能出现重复的奇葩值,便是环形链表。不合常理,理论上解析2应该比解析1速度更快。 40 | 41 | | |复杂度|大小|百分比| 42 | |--|--|--|--| 43 | |时间|$O(n)$|44 ms|85.11%| 44 | |空间|$O(n)$|18.1 MB|32.48%| 45 | 46 | ```python 47 | class Solution(object): 48 | def hasCycle(self, head): 49 | if not head: 50 | return False 51 | 52 | while head: 53 | if head.val == 'like': return True 54 | else: head.val = 'like' 55 | head = head.next 56 | return False 57 | ``` 58 | 59 | ### 解析3: 60 | 遍历链表,依次将链表结点保存在一个Hash表中,如果新结点在Hash表中出现过,则出现了环,否则环不存在。 61 | 62 | | |复杂度|大小|百分比| 63 | |--|--|--|--| 64 | |时间|$O(n)$|48 ms|71.11%| 65 | |空间|$O(n)$|18.5 MB|5.48%| 66 | 67 | ```python 68 | class Solution(object): 69 | def hasCycle(self, head): 70 | node = set() 71 | while head: 72 | if head in node: 73 | return True 74 | node.add(head) 75 | head = head.next 76 | return False 77 | ``` 78 | -------------------------------------------------------------------------------- /python/142. 环形链表 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 3 | 4 | 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 5 | 6 | 说明:不允许修改给定的链表。 7 |   8 | ``` 9 | 示例 1: 10 | 11 | 输入:head = [3,2,0,-4], pos = 1 12 | 输出:tail connects to node index 1 13 | 解释:链表中有一个环,其尾部连接到第二个节点。 14 | ``` 15 | 16 | ### 解析1: 17 | 遍历链表,将每一个遍历的结点加入到hash表的中,然后判断新结点是否在Hash表中,如果在则环的入口,不在则返回空。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(n)$|40 ms|96.46%| 22 | |空间|$O(n)$|18.7 MB|7.69%| 23 | 24 | 25 | ```python 26 | class Solution(object): 27 | def detectCycle(self, head): 28 | node = set() 29 | 30 | while head: 31 | if head in node: 32 | return head 33 | node.add(head) 34 | head = head.next 35 | return 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /python/144. 二叉树的前序遍历.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 3 | 给定一个二叉树,返回它的 前序 遍历。 4 | 5 | ``` 6 | 示例: 7 | 8 | 输入: [1,null,2,3] 9 | 1 10 | \ 11 | 2 12 | / 13 | 3 14 | 15 | 输出: [1,2,3] 16 | ``` 17 | 18 | ### 解析1: 19 | 依然是递归解法,写起啦较简单。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|36 ms|35.62%| 24 | |空间|$O(n)$|11.9 MB|11.43%| 25 | 26 | ```python 27 | class Solution(object): 28 | def preorderTraversal(self, root): 29 | res = [] 30 | def dfs(root): 31 | if not root: 32 | return None 33 | res.append(root.val) 34 | dfs(root.left) 35 | dfs(root.right) 36 | dfs(root) 37 | return res 38 | ``` 39 | 40 | ### 解析2: 41 | 迭代实现。使用栈保存每一个结点,依次保存右结点和左结点。 42 | 43 | | |复杂度|大小|百分比| 44 | |--|--|--|--| 45 | |时间|$O(n)$|28 ms|35.62%| 46 | |空间|$O(n)$|11.7MB|32.69%| 47 | 48 | 步骤: 49 | 1. 栈保存根节点; 50 | 2. 遍历根结点: 51 | 1. pop最新结点,然后保存结点值; 52 | 2. 如果右子树不为空,右结点入栈; 53 | 3. 如果左子树不为空,左结点入栈; 54 | 55 | ```python 56 | class Solution(object): 57 | def preorderTraversal(self, root): 58 | if not root: 59 | return None 60 | res = [] 61 | stack = [root] 62 | while stack: 63 | node = stack.pop() 64 | res.append(node.val) 65 | if node.right: 66 | stack.append(node.right) 67 | if node.left: 68 | stack.append(node.left) 69 | return res 70 | ``` -------------------------------------------------------------------------------- /python/145. 二叉树的后序遍历.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,返回它的 后序 遍历。 3 | ``` 4 | 示例: 5 | 输入: [1,null,2,3] 6 | 1 7 | \ 8 | 2 9 | / 10 | 3 11 | 12 | 输出: [3,2,1] 13 | ``` 14 | | |复杂度|大小|百分比| 15 | |--|--|--|--| 16 | |时间|$O(n)$|28 ms|35.62%| 17 | |空间|$O(n)$|11.7MB|32.69%| 18 | 19 | ### 解析1: 20 | 递归实现,对于递归而言,只是res的位置不同。 21 | 22 | | |复杂度|大小|百分比| 23 | |--|--|--|--| 24 | |时间|$O(n)$|32 ms|35.62%| 25 | |空间|$O(n)$|11.7MB|32.69%| 26 | 27 | 28 | ```python 29 | class Solution(object): 30 | def postorderTraversal(self, root): 31 | res = [] 32 | def dfs(root): 33 | if not root: 34 | return None 35 | dfs(root.left) 36 | dfs(root.right) 37 | res.append(root.val) 38 | dfs(root) 39 | 40 | return res 41 | ``` 42 | 43 | ### 解析2: 44 | 循环实现,和前序遍历的不同依然是连接val的位置不同。有点迷这个解法。 45 | 46 | ```python 47 | class Solution(object): 48 | def postorderTraversal(self, root): 49 | if not root: 50 | return [] 51 | 52 | res = [] 53 | stack = [root] 54 | while stack: 55 | node = stack.pop() 56 | if node.left: 57 | stack.append(node.left) 58 | if node.right: 59 | stack.append(node.right) 60 | res.append(node.val) 61 | return res[::-1] 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /python/146. LRU缓存机制.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 3 | 4 | 获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 5 | 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。 6 | 7 | 进阶: 8 | 9 | 你是否可以在 O(1) 时间复杂度内完成这两种操作? 10 | 11 | ``` 12 | 示例: 13 | 14 | LRUCache cache = new LRUCache( 2 /* 缓存容量 */ ); 15 | 16 | cache.put(1, 1); 17 | cache.put(2, 2); 18 | cache.get(1); // 返回 1 19 | cache.put(3, 3); // 该操作会使得密钥 2 作废 20 | cache.get(2); // 返回 -1 (未找到) 21 | cache.put(4, 4); // 该操作会使得密钥 1 作废 22 | cache.get(1); // 返回 -1 (未找到) 23 | cache.get(3); // 返回 3 24 | cache.get(4); // 返回 4 25 | ``` 26 | 27 | ### 解析1: 28 | 使用有序字典实现,Collections.OrderedDict()。get,最近使用过的key,移动到末尾。 29 | 30 | | |复杂度|大小|百分比| 31 | |--|--|--|--| 32 | |时间|$O(1)$|248 ms|67.94%| 33 | |空间|$O(n)$|22.8 MB|5.64%| 34 | 35 | ```python 36 | import collections 37 | class LRUCache: 38 | def __init__(self, capacity: int): 39 | self.dic = collections.OrderedDict() 40 | self.cap = capacity 41 | 42 | def get(self, key: int) -> int: 43 | if key not in self.dic: 44 | return -1 45 | self.dic.move_to_end(key) 46 | return self.dic[key] 47 | 48 | def put(self, key: int, value: int) -> None: 49 | if key in self.dic: 50 | del self.dic[key] 51 | 52 | if len(self.dic) >= self.cap: 53 | self.dic.popitem(0) 54 | 55 | self.dic[key] = value 56 | ``` 57 | 58 | ### 有序字典OrderedDict: 59 | -------------------------------------------------------------------------------- /python/148. 排序链表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: 4->2->1->3 8 | 输出: 1->2->3->4 9 | 10 | 示例 2: 11 | 12 | 输入: -1->5->3->4->0 13 | 输出: -1->0->3->4->5 14 | ``` 15 | 16 | ### 解析1: 17 | 将链表结点保存,然后排序,再重新生成链表。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(nlogn)$|96ms|96.18%| 22 | |空间|$O(n)$|37 MB|10.72%| 23 | 24 | ```python 25 | class Solution(object): 26 | def sortList(self, head): 27 | nums = [] 28 | while head: 29 | nums.append(head.val) 30 | head = head.next 31 | 32 | res = node = ListNode(0) 33 | for num in sorted(nums): 34 | node.next = ListNode(num) 35 | node = node.next 36 | return res.next 37 | ``` -------------------------------------------------------------------------------- /python/152. 乘积最大子序列.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。 3 | 4 | 示例 1: 5 | 6 | 输入: [2,3,-2,4] 7 | 输出: 6 8 | 解释: 子数组 [2,3] 有最大乘积 6。 9 | 10 | ### 解析1: 11 | 暴力法,遍历每一个子序列,然后求乘积。 12 | 13 | | |复杂度|大小|百分比| 14 | |--|--|--|--| 15 | |时间|$O(n^3)$|超时| | 16 | |空间|$O(1)$| | | 17 | 18 | ```python 19 | class Solution(object): 20 | def maxProduct(self, A): 21 | res = float('-inf') 22 | length = len(A) 23 | for i in range(length): 24 | for j in range(i+1, length+1): 25 | temp = 1 26 | for k in range(i, j): 27 | temp *= A[k] 28 | res = max(res, temp) 29 | return res 30 | ``` 31 | 32 | ### 解析2: 33 | 动态规划,借助最大最小值,fmax(i)以i为结尾的连续子序列的最大值,fmin(i)连续子序列最小值。主要是下列状态转移方程。 34 | ``` 35 | fmax(i) = max(fmax(i-1)*num[i], fmin(i-1)*num[i], num[i]) 36 | fmin(i) = min(fmax(i-1)*num[i], fmin(i-1)*num[i], num[i]) 37 | ``` 38 | 39 | | |复杂度|大小|百分比| 40 | |--|--|--|--| 41 | |时间|$O(n)$|80 ms| 55.76%| 42 | |空间|$O(1)$|14.1 MB | 28.65%| 43 | 44 | 45 | ```python 46 | class Solution: 47 | def maxProduct(self, nums: List[int]) -> int: 48 | if not nums: return 49 | res = nums[0] 50 | pre_max = nums[0] 51 | pre_min = nums[0] 52 | for num in nums[1:]: 53 | cur_max = max(pre_max * num, pre_min * num, num) 54 | cur_min = min(pre_max * num, pre_min * num, num) 55 | res = max(res, cur_max) 56 | pre_max = cur_max 57 | pre_min = cur_min 58 | return res 59 | ``` -------------------------------------------------------------------------------- /python/155. 最小栈.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 3 | 4 | push(x) -- 将元素 x 推入栈中。 5 | pop() -- 删除栈顶的元素。 6 | top() -- 获取栈顶元素。 7 | getMin() -- 检索栈中的最小元素。 8 | 9 | >示例: 10 | MinStack MinStack = new MinStack(); 11 | MinStack.push(-2); 12 | MinStack.push(0); 13 | MinStack.push(-3); 14 | MinStack.getMin(); --> 返回 -3. 15 | MinStack.pop(); 16 | MinStack.top(); --> 返回 0. 17 | MinStack.getMin(); --> 返回 -2. 18 | 19 | ### 解析1: 20 | 建立一个辅助栈,每次将已经压入栈内的最小值压入辅助栈。 21 | 22 | * **算法流程:** 23 | 1. 主要是push的时候,如果辅助栈为空或者新元素小于辅助栈顶元素的时候push新元素,否则push辅助栈顶元素; 24 | 2. pop两个栈均pop,注意栈为空的情况 25 | 26 | * **复杂度:** 27 | | |复杂度|大小|百分比| 28 | |--|--|--|--| 29 | |时间|$O(1)$|116 ms|31.87%| 30 | |空间|$O(1)$|16.9 MB|100%| 31 | 32 | ```python 33 | class MinStack(object): 34 | def __init__(self): 35 | self.stack = [] 36 | self.MinStack = [] 37 | 38 | def push(self, x): 39 | """ 40 | :type x: int 41 | :rtype: None 42 | """ 43 | self.stack.append(x) 44 | if self.MinStack == [] or x < self.MinStack[-1]: 45 | self.MinStack.append(x) 46 | else: 47 | self.MinStack.append(self.MinStack[-1]) 48 | 49 | def pop(self): 50 | # write code here 51 | if self.stack == None or self.MinStack == None: 52 | return None 53 | self.MinStack.pop() 54 | self.stack.pop() 55 | 56 | def top(self): 57 | # write code here 58 | return self.stack[-1] 59 | 60 | def getMin(self): 61 | # write code here 62 | return self.MinStack[-1] 63 | ``` 64 | -------------------------------------------------------------------------------- /python/167. 两数之和 II - 输入有序数组.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 3 | 4 | 函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 5 | 6 | 说明: 7 | 8 | 返回的下标值(index1 和 index2)不是从零开始的。 9 | 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 10 | ``` 11 | 示例: 12 | 13 | 输入: numbers = [2, 7, 11, 15], target = 9 14 | 输出: [1,2] 15 | 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 16 | ``` 17 | 18 | ### 解析1: 19 | 和第一题一样,采用相同的方式,将数值和索引作为key和value保存在字典中。遍历数组,然后跑判断差是否在字典中出现过,同时判断索引是否一致。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|96 ms|60.50%| 24 | |空间|$O(n)$|14.2 MB|5.22%| 25 | 26 | ```python 27 | class Solution: 28 | def twoSum(self, nums, target): 29 | nums_dict = dict() 30 | for index in range(len(nums)): 31 | if nums[index] not in nums_dict: 32 | nums_dict[nums[index]] = index 33 | temp = target-nums[index] 34 | if temp in nums_dict and nums_dict[temp] != index: 35 | return sorted([index+1,nums_dict[temp]+1]) 36 | ``` 37 | 38 | ### 解析2: 39 | 双指针。左右指针,分别编列,然后判断,收缩。 40 | 41 | | |复杂度|大小|百分比| 42 | |--|--|--|--| 43 | |时间|$O(n)$|96 ms|60.50%| 44 | |空间|$O(n)$|14.2 MB|5.22%| 45 | 46 | ```python 47 | class Solution: 48 | def twoSum(self, nums, target): 49 | left = 0 50 | right = len(nums)-1 51 | while left < right: 52 | temp = nums[left] + nums[right] 53 | if temp == target: 54 | return [left+1, right+1] 55 | elif temp < target: 56 | left += 1 57 | elif temp > target: 58 | right -= 1 59 | return [-1,-1] 60 | ``` -------------------------------------------------------------------------------- /python/171. Excel表列序号.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个Excel表格中的列名称,返回其相应的列序号。 3 | ``` 4 | 例如, 5 | 6 | A -> 1 7 | B -> 2 8 | C -> 3 9 | ... 10 | Z -> 26 11 | AA -> 27 12 | AB -> 28 13 | ... 14 | 示例 1: 15 | 16 | 输入: "A" 17 | 输出: 1 18 | 示例 2: 19 | 20 | 输入: "AB" 21 | 输出: 28 22 | ``` 23 | 24 | ### 解析1: 25 | 26进制转化成10进制,和进制转化一样,正常操作即可。 26 | 27 | | |复杂度|大小|百分比| 28 | |--|--|--|--| 29 | |时间|$O(n)$|32 ms|99.9%| 30 | |空间|$O(n)$|14 MB|6.10%| 31 | 32 | ```python 33 | class Solution: 34 | def titleToNumber(self, s: str) -> int: 35 | res = 0 36 | temp = 1 37 | for c in s[::-1]: 38 | res += (ord(c)-64)*temp 39 | temp = temp*26 40 | return res 41 | ``` 42 | 43 | 对代码进行简化。 44 | ```python 45 | class Solution: 46 | def titleToNumber(self, s: str) -> int: 47 | return sum( (ord(a) - 64) * (26 ** i) for i, a in enumerate(s[::-1])) 48 | ``` -------------------------------------------------------------------------------- /python/172. 阶乘后的零.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数 n,返回 n! 结果尾数中零的数量。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: 3 7 | 输出: 0 8 | 解释: 3! = 6, 尾数中没有零。 9 | 示例 2: 10 | 11 | 输入: 5 12 | 输出: 1 13 | 解释: 5! = 120, 尾数中有 1 个零. 14 | ``` 15 | 16 | ### 解析1: 17 | 求阶乘的每个元素有多少个因子是2和5,最后0的个数就是2和5因子数的最小值。但是超时啦。 18 | 19 | ```python 20 | class Solution: 21 | def trailingZeroes(self, n: int) -> int: 22 | two = 0 23 | five = 0 24 | for i in range(1,n+1): 25 | while i%2 == 0 or i%5 == 0: 26 | if i%2 == 0: 27 | two += 1 28 | i = i//2 29 | if i %5 == 0: 30 | five += 1 31 | i = i // 5 32 | return min(two,five) 33 | ``` 34 | 35 | ### 解析2: 36 | 得到阶乘后的结果,然后求尾部有多少个0,但是依然超时了。 37 | 38 | ```python 39 | class Solution: 40 | def trailingZeroes(self, n: int) -> int: 41 | res = 1 42 | for i in range(1,n+1): 43 | res *= i 44 | cnt = 0 45 | for c in str(res)[::-1]: 46 | if c == '0': 47 | cnt += 1 48 | else: 49 | break 50 | return cnt 51 | ``` 52 | 53 | ### 解析3: 54 | 求5的个数,求一共有多少个。 55 | 56 | | |复杂度|大小|百分比| 57 | |--|--|--|--| 58 | |时间|$O(logn)$|44 ms|86.77%| 59 | |空间|$O(n)$|13.8 MB|5.19%| 60 | 61 | 62 | ```python 63 | class Solution(object): 64 | def trailingZeroes(self, n): 65 | ans = 0 66 | while n>=5: 67 | n //= 5 68 | ans += n 69 | return ans 70 | ``` -------------------------------------------------------------------------------- /python/189. 旋转数组.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: [1,2,3,4,5,6,7] 和 k = 3 7 | 输出: [5,6,7,1,2,3,4] 8 | 解释: 9 | 向右旋转 1 步: [7,1,2,3,4,5,6] 10 | 向右旋转 2 步: [6,7,1,2,3,4,5] 11 | 向右旋转 3 步: [5,6,7,1,2,3,4] 12 | ``` 13 | 14 | ### 解析1: 15 | 简单,直接旋转。要原地操作,注意如何写。 16 | 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(logn)$|72 ms|91.24%| 20 | |空间|$O(n)$|15.1 MB|5.18%| 21 | 22 | ```python 23 | class Solution: 24 | def rotate(self, nums: List[int], k: int) -> None: 25 | k = k%len(nums) 26 | nums[:] = nums[-k:] + nums[:-k] 27 | ``` -------------------------------------------------------------------------------- /python/19. 删除链表的倒数第N个节点.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 3 | 4 | >示例: 5 | 给定一个链表: 1->2->3->4->5, 和 n = 2. 6 | 当删除了倒数第二个节点后,链表变为 1->2->3->5. 7 | 8 | ### 解析1: 9 | 利用双指针,第一个指针从哑结点先走n步,然后第二个指针从哑结点同步开始走。第一个指针到最后一个结点时,第二个到达倒数第k+1个。哑结点到设置哑结点,找到倒数k+1个结点,然后令倒数第k+1个结点的next值为后两个结点。 10 | 11 | | |复杂度|大小|百分比| 12 | |--|--|--|--| 13 | |时间|$O(n)$|28 ms|55.33%| 14 | |空间|$O(n)$|11.7 MB|34.34%| 15 | 16 | 步骤: 17 | 1. 设置哑结点,dummy = ListNode(0),dummy.next = head 18 | 2. 双指针,第一个指针从0开始走n个,到达head的第n个结点; 19 | 3. 另一个指针从0开始和第一个指针同时开始走,如果第一指针到末尾,第二个指针正好到倒数第k+1个。 20 | 21 | 此题目未考虑边界条件,会出现越界的情况。 22 | ```python 23 | class Solution(object): 24 | def removeNthFromEnd(self, head, n): 25 | dummy = ListNode(-1) 26 | dummy.next = head 27 | fast = dummy 28 | while n: 29 | fast = fast.next 30 | n -= 1 31 | slow = dummy 32 | while fast.next: 33 | fast = fast.next 34 | slow = slow.next 35 | slow.next = slow.next.next 36 | return dummy.next 37 | ``` 38 | 39 | ### 解析2: 40 | 先求结点个数L,然后去掉倒数第n个即正数第L-n+1个。遍历到第L-n个结点,然后跨过第l-n+1个结点。 41 | 42 | 步骤: 43 | 1. 求结点个数; 44 | 2. 从哑结点开始,遍历到第L-n个结点head; 45 | 3. head.next = head.next.next 46 | 47 | 48 | ```python 49 | class Solution(object): 50 | def removeNthFromEnd(self, head, n): 51 | 52 | if not head:return 53 | L = 0 54 | dummy = ListNode(0) 55 | dummy.next = head 56 | head1 = dummy 57 | 58 | while head: 59 | head = head.next 60 | L += 1 61 | for _ in range(L-n): 62 | head1 = head1.next 63 | head1.next = head1.next.next 64 | 65 | return dummy.next 66 | ``` 67 | 68 | ### 总结: 69 | 题目不麻烦,两个解法思路大同小异,主要是确定结点值的界限,和《剑指offer》找倒数第k个结点相似,这个是找到倒数第k+1个结点,然后跳过倒数第k个结点。需要注意边界条件,不过leetcode没有设置异常值。 -------------------------------------------------------------------------------- /python/190. 颠倒二进制位.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 颠倒给定的 32 位无符号整数的二进制位。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: 00000010100101000001111010011100 8 | 输出: 00111001011110000010100101000000 9 | 解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596, 10 | 因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。 11 | ``` 12 | ### 解析1: 13 | * **算法流程:** 14 | * bin(n):二进制表示 15 | * str.zfill(n):字符串填充,在左边用0补全; 16 | * [::-1]:翻转 17 | * int:转换成十进制 18 | 19 | 20 | * **复杂度:** 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|32 ms|81%| 24 | |空间|$O(n)$|13.6 MB|16%| 25 | 26 | 27 | ```python 28 | class Solution: 29 | def reverseBits(self, n: int) -> int: 30 | return int(bin(n)[2:].zfill(32)[::-1],base=2) 31 | ``` 32 | 33 | ### 二进制的python语法: 34 | * bin(x):返回数x的二进制表示; 35 | * x^y:两个十进制的数对应的二进制位做异或; 36 | -------------------------------------------------------------------------------- /python/191. 位1的个数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入:00000000000000000000000000001011 8 | 输出:3 9 | 解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 10 | ``` 11 | 12 | ### 解析1: 13 | 转变成二进制然后直接返回,利用了python的内部机制,这个操作太骚了。 14 | 15 | | |复杂度|大小|百分比| 16 | |--|--|--|--| 17 | |时间|$O(n)$|24 ms|65.73%| 18 | |空间|$O(n)$|11.7 MB|36.16%| 19 | 20 | ```python 21 | class Solution(object): 22 | def hammingWeight(self, n): 23 | return bin(n).count('1') 24 | ``` 25 | 26 | ### 解析2: 27 | * **算法流程:** 28 | 通过位运算来判断有多少个1,明显更费时。将x和1与,如果不是1,则n的二进制的最右边一位不是0,然后向右移位依次判断最右边是否是1即可。复杂度还是$O(n)$但是每次需要判断是否是1,还是很费时的。 29 | 30 | * **复杂度:** 31 | | |复杂度|大小|百分比| 32 | |--|--|--|--| 33 | |时间|$O(n)$|36 ms|11.38%| 34 | |空间|$O(n)$|11.7 MB|36.16%| 35 | 36 | ```python 37 | class Solution(object): 38 | def hammingWeight(self, n): 39 | count = 0 40 | while n: 41 | count += n&1 42 | n >>= 1 43 | return count 44 | ``` 45 | 46 | -------------------------------------------------------------------------------- /python/198. 打家劫舍.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 3 | 4 | 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。 5 | 6 | >示例 1: 7 | 输入: [1,2,3,1] 8 | 输出: 4 9 | 解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 10 |   偷窃到的最高金额 = 1 + 3 = 4 11 | 12 | 13 | ### 解析1: 14 | 动态规划,主要是得到状态转移方程,如果直接得到比较麻烦,就从0开始推导,如下: 15 | 设$f(n)$为从前n个房子中抢到的最大数额; 16 | * n=1:$f(1) = nums[0]$ 17 | * n=2:$f(2) = max(nums[0], nums[1])$ 18 | * n=3:$f(3) = max(f(1)+nums[2], f(2))$,两个选择,抢还是不抢第三个房子,如果抢则是$f(1)+nums[2]$,不抢则是$f(2)$,然后比较二者的大小 19 | 20 | 由此便可得到动态规划的状态转移方程: $f(n) = max(f(n-1), f(n-2)+nums[n])$。 21 | 22 | 动态规划的题目,主要是得到状态转移方程和状态含义,目前常见的两种,前n个数中的最优解,以第n个数为结尾的最优解。 23 | 24 | 可以用一个list保存对应的最大值,但是仅用两个数表示$f(n-1),f(n-2)$即可。初始化前两个数为0可以简化操作。 25 | 26 | | |复杂度|大小|百分比| 27 | |--|--|--|--| 28 | |时间|$O(n)$|28 ms| 43.76%| 29 | |空间|$O(1)$|11.7 MB | 28.65%| 30 | 31 | ```python 32 | class Solution(object): 33 | def rob(self, nums): 34 | a,b,length = 0,0,len(nums) 35 | for i in range(length): 36 | a,b = b, max(a+nums[i], b) 37 | return b 38 | ``` 39 | 40 | 41 | -------------------------------------------------------------------------------- /python/20.有效的括号.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 3 | 4 | 有效字符串需满足: 5 | 6 | 左括号必须用相同类型的右括号闭合。 7 | 左括号必须以正确的顺序闭合。 8 | 注意空字符串可被认为是有效字符串。 9 | 10 | >示例 1: 11 | 输入: "()" 12 | 输出: true 13 | 14 | ### 解析1: 15 | 建立一个栈保存每一个符号,如果新符号和栈顶元素是否可消去,通过字典对来实现。 16 | 17 | 步骤: 18 | 1. 建立一个字典对,来判断是否可以相消,{")": "(", "}": "{", "]": "["}; 19 | 2. 遍历每一个符号: 20 | 1. 比较每一个符号在字典中值value和栈顶元素是否相等,如果相等则可以相消; 21 | 2. 若可以消去,则栈顶出栈,否则添加新元素; 22 | 3. 判断栈是否为空,因为若是有效括号,所有符号会相消; 23 | 24 | | |复杂度|大小|百分比| 25 | |--|--|--|--| 26 | |时间|$O(n)$|20 ms|93.73%| 27 | |空间|$O(1)$|11.8 MB|33.20%| 28 | 29 | ```python 30 | class Solution(object): 31 | def isValid(self, s): 32 | s_dict = {')':'(','}':'{',"]":'['} 33 | stack = [] 34 | for char in s: 35 | if stack and char in s_dict and s_dict[char] == stack[-1]: 36 | stack.pop() 37 | else: 38 | stack.append(char) 39 | return len(stack) == 0 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /python/200. 岛屿数量.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: 8 | 11110 9 | 11010 10 | 11000 11 | 00000 12 | 13 | 输出: 1 14 | 示例 2: 15 | 16 | 输入: 17 | 11000 18 | 11000 19 | 00100 20 | 00011 21 | 22 | 输出: 3 23 | ``` 24 | 25 | ### 解析1: 26 | dfs,遍历每一个点,如果是1,令其周围的1都变成0(同一个岛屿),让1周围的1都变成0以后,不和这个1直接相邻的点才不会被改变,也就是下一个岛屿。 27 | 28 | * **算法流程:** 29 | 1. 遍历每一个点,如果该点值为1,计数+1,对他周围的点进行回溯,将周围的1都变成0, 30 | 2. 将每一个回溯经过的1变成0,另一个岛屿由于不是接壤的所以不会被改变成0 31 | 32 | * **复杂度:** 33 | | |复杂度|大小|百分比| 34 | |--|--|--|--| 35 | |时间|$O(mn)$|168 ms|79.90%| 36 | |空间|$O(mn)$|15.2 MB|14.65%| 37 | 38 | 39 | ```python 40 | class Solution: 41 | def numIslands(self, grid: List[List[str]]) -> int: 42 | n = len(grid) 43 | if not n:return 0 44 | m = len(grid[0]) 45 | def helper(i,j): 46 | grid[i][j] = '0' 47 | for dx,dy in [(0,1),(0,-1),(1,0),(-1,0)]: 48 | nx,ny = i+dx,j+dy 49 | if 0<=nx int: 35 | t = 0 36 | while m != n: 37 | m >>= 1 38 | n >>= 1 39 | t += 1 40 | return n << t 41 | ``` 42 | -------------------------------------------------------------------------------- /python/202. 快乐数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 编写一个算法来判断一个数是不是“快乐数”。 3 | 4 | 一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。 5 | ``` 6 | 示例:  7 | 8 | 输入: 19 9 | 输出: true 10 | 解释: 11 | 12 + 92 = 82 12 | 82 + 22 = 68 13 | 62 + 82 = 100 14 | 12 + 02 + 02 = 1 15 | ``` 16 | 17 | ### 解析1: 18 | 用规律求解,在不知道的情况下,是比较麻烦的。这里面有一个规律。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(logn)$|44 ms|96.54%| 23 | |空间|$O(n)$|13.8 MB|5.56%| 24 | 25 | 26 | **规律:** 27 | 1. [1,4]之间的数只有1是快乐数,其他均不是,3和4会进入一个循环序列; 28 | 2. [4,+无穷]非快乐数会进入3,4的循环,快乐数会得到1; 29 | 30 | 31 | ```python 32 | class Solution: 33 | def isHappy(self, n: int) -> bool: 34 | return self.isHappy(sum([int(x)**2 for x in str(n)])) if n > 4 else n == 1 35 | ``` 36 | -------------------------------------------------------------------------------- /python/204. 计数质数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 统计所有小于非负整数 n 的质数的数量。 3 | 4 | 示例: 5 | 6 | 输入: 10 7 | 输出: 4 8 | 解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。 9 | 10 | ### 解析1: 11 | 常规超时做法,遍历每一个数,然后看起是否是质数。 12 | 13 | ```python 14 | class Solution: 15 | def countPrimes(self, n: int) -> int: 16 | res = 0 17 | for i in range(2, n): 18 | for j in range(2, i): 19 | if i % j == 0: 20 | break 21 | else: 22 | #print(i) 23 | res += 1 24 | return res 25 | ``` 26 | 27 | ### 解析2: 28 | 设置一个质数数组,从2开始,2是质数True,那么2的倍数均不是质数,令2的所有倍数为False,继续遍历,得到一个质数数组。 29 | ```python 30 | class Solution: 31 | def countPrimes(self, n: int) -> int: 32 | isPrimes = [1] * n 33 | res = 0 34 | for i in range(2, n): 35 | if isPrimes[i] == 1: res += 1 36 | j = i 37 | while i * j < n: 38 | isPrimes[i * j] = 0 39 | j += 1 40 | return res 41 | ``` 42 | 43 | 优化后的版本: 44 | ```python 45 | class Solution: 46 | def countPrimes(self, n: int) -> int: 47 | if n < 2: return 0 48 | isPrimes = [1] * n 49 | isPrimes[0] = isPrimes[1] = 0 50 | for i in range(2, int(n ** 0.5) + 1): 51 | if isPrimes[i] == 1: 52 | isPrimes[i * i: n: i] = [0] * len(isPrimes[i * i: n: i]) 53 | return sum(isPrimes) 54 | ``` -------------------------------------------------------------------------------- /python/206. 反转链表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 反转一个单链表。 3 | 4 | >示例: 5 | 输入: 1->2->3->4->5->NULL 6 | 输出: 5->4->3->2->1->NULL 7 | 8 | ### 解析1: 9 | 对原始链表的处理。循环简单求解。 10 | * **算法流程:** 11 | * 1. None赋值给res(目标链表) 12 | * 2. 遍历原链表 13 | * 3. 保存原链表结点值的next 14 | * 4. 原链表当前结点的next,指向res; 15 | * 5. 当前结点赋值给res; 16 | * 6. 原链表结点的next,赋值给当前结点 17 | 18 | 19 | * **复杂度:** 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|24 ms|91.65%| 23 | |空间|$O(n)$|13.5 MB|46.06%| 24 | 25 | ```python 26 | class Solution: 27 | # 返回ListNode 28 | def ReverseList(self, pHead): 29 | # write code here 30 | if pHead is None: 31 | return pHead 32 | last = None #指向上一个节点 33 | while pHead: 34 | # 先用tmp保存pHead的下一个节点的信息, 35 | # 保证单链表不会因为失去pHead节点的next而就此断裂 36 | tmp = pHead.next 37 | # 保存完next,就可以让pHead的next指向last了 38 | pHead.next = last 39 | # pHead赋值给last 40 | last = pHead 41 | # 对pHead进行更新,获取下一个结点 42 | pHead = tmp 43 | return last 44 | ``` 45 | 46 | 上述代码利用了python的特性,赋值的时候不用考虑先后的问题。下面用temp保存pHead.next结点。 47 | ```python 48 | class Solution(object): 49 | def reverseList(self, head): 50 | res = None 51 | while head:res,res.next, head = head, res, head.next 52 | return res 53 | ``` 54 | 55 | ### 解析2: 56 | 递归求解。 57 | 58 | ```python 59 | class Solution: 60 | def ReverseList(self, pHead): 61 | # write code here 62 | if not pHead or not pHead.next: 63 | return pHead 64 | else: 65 | newHead = self.ReverseList(pHead.next) 66 | pHead.next.next=pHead 67 | pHead.next=None 68 | return newHead 69 | ``` -------------------------------------------------------------------------------- /python/207. 课程表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 现在你总共有 n 门课需要选,记为 0 到 n-1。 3 | 4 | 在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1] 5 | 6 | 给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习? 7 | 8 | ``` 9 | 示例 1: 10 | 11 | 输入: 2, [[1,0]] 12 | 输出: true 13 | 解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。 14 | 示例 2: 15 | 16 | 输入: 2, [[1,0],[0,1]] 17 | 输出: false 18 | 解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。 19 | ``` 20 | 21 | ### 解析1: 22 | 拓扑排序,每一次从图中删除入度为0的点,即这些点不需要前修课。删除该节点,然后令和此结点相连的结点的入度-1。如果入度为0即进入队列,最后判断队列的大小和numCourse的大小。 23 | 24 | E:边数,V:结点数 25 | | |复杂度|大小|百分比| 26 | |--|--|--|--| 27 | |时间|$O(E+V)$|264 ms|22.52%| 28 | |空间|$O(V)$|15 MB|35.00%| 29 | 30 | 31 | ```python 32 | class Solution: 33 | def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: 34 | # 统计入度数 35 | indegrees = [0 for _ in range(numCourses)] 36 | # 邻接表 37 | adjacency = [[] for _ in range(numCourses)] 38 | queue = [] 39 | # Get the indegree and adjacency of every course. 40 | for cur, pre in prerequisites: 41 | # 统计入度,后面一个点是要求必修的课,统计前面的点入度 42 | indegrees[cur] += 1 43 | # 邻接表:统计每个点指向的课程结点 44 | adjacency[pre].append(cur) 45 | 46 | # 统计入度为0的点,这些课程不需要先修课,所以可以直接放入queue中 47 | for i in range(len(indegrees)): 48 | if not indegrees[i]: queue.append(i) 49 | 50 | # 依次将queue中的点出队,然后将它所连的课程结点入度-1; 51 | while queue: 52 | pre = queue.pop(0) 53 | numCourses -= 1 54 | for cur in adjacency[pre]: 55 | indegrees[cur] -= 1 56 | if not indegrees[cur]: queue.append(cur) 57 | return not numCourses 58 | ``` -------------------------------------------------------------------------------- /python/208.前缀树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。 3 | ``` 4 | 示例: 5 | 6 | Trie trie = new Trie(); 7 | 8 | trie.insert("apple"); 9 | trie.search("apple"); // 返回 true 10 | trie.search("app"); // 返回 false 11 | trie.startsWith("app"); // 返回 true 12 | trie.insert("app"); 13 | trie.search("app"); // 返回 true 14 | ``` 15 | 16 | ### 解析1: 17 | emmm很直男的做法,直接进行求解的,判断是否在一个set里面,前缀也可以同样比较。没有使用数据结构,时间效率明显很低。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(n)$|1388 ms|5.23%| 22 | |空间|$O(1)$|20.2 MB|92.83%| 23 | 24 | ```python 25 | class Trie: 26 | 27 | def __init__(self): 28 | self.set = set() 29 | 30 | def insert(self, word: str) -> None: 31 | self.set.add(word) 32 | 33 | def search(self, word: str) -> bool: 34 | return word in self.set 35 | 36 | def startsWith(self, prefix: str) -> bool: 37 | for word in self.set: 38 | if prefix == word[:len(prefix)]: 39 | return True 40 | return False 41 | ``` -------------------------------------------------------------------------------- /python/216. 组合总和 III.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 3 | 4 | 说明: 5 | 6 | 所有数字都是正整数。 7 | 解集不能包含重复的组合。  8 | 9 | ``` 10 | 示例 1: 11 | 12 | 输入: k = 3, n = 7 13 | 输出: [[1,2,4]] 14 | 示例 2: 15 | 16 | 输入: k = 3, n = 9 17 | 输出: [[1,2,6], [1,3,5], [2,3,4]] 18 | ``` 19 | 20 | ### 解析1: 21 | 依然是回溯法,可使用万能模板,和其他没什么两样。加一个判断条件,判断n是否为0以及temp长度是否是k。 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O()$|32 ms|35.23%| 26 | |空间|$O(1)$|11.8 MB|30.66%| 27 | 28 | ```python 29 | class Solution(object): 30 | def combinationSum3(self, k, n): 31 | res = [] 32 | nums = list(range(1, 10)) 33 | 34 | def backtrack(nums, temp, n): 35 | if n == 0 and len(temp) == k: 36 | res.append(temp) 37 | else: 38 | for i in range(len(nums)): 39 | backtrack(nums[i+1:], temp+[nums[i]], n-nums[i]) 40 | 41 | backtrack(nums, [], n) 42 | return res 43 | ``` -------------------------------------------------------------------------------- /python/219. 存在重复元素 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: nums = [1,2,3,1], k = 3 8 | 输出: true 9 | ``` 10 | 11 | ### 解析1: 12 | 哈希表存储每个数出现的位置,判断是否在哈希表中出现过,同时判断索引是否满足条件。 13 | 14 | | |复杂度|大小|百分比| 15 | |--|--|--|--| 16 | |时间|$O(n)$|136 ms|19.91%| 17 | |空间|$O(n)$|16.7 MB|8.53%| 18 | 19 | ```python 20 | class Solution(object): 21 | def containsNearbyDuplicate(self, nums, k): 22 | """ 23 | :type nums: List[int] 24 | :type k: int 25 | :rtype: bool 26 | """ 27 | cnt = {} 28 | for i in range(len(nums)): 29 | if nums[i] in cnt and i - cnt[nums[i]] <= k: 30 | return True 31 | cnt[nums[i]] = i 32 | return False 33 | ``` -------------------------------------------------------------------------------- /python/220. 存在重复元素 III.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: nums = [1,2,3,1], k = 3, t = 0 7 | 输出: true 8 | ``` 9 | 10 | ### 解析1: 11 | 暴力法,遍历两边数组,然后判断是否满足大小和索引关系。直接超时。 12 | 13 | | |复杂度|大小|百分比| 14 | |--|--|--|--| 15 | |时间|$O(n min(k,n))$|超时|| 16 | |空间|$O(1)$||| 17 | 18 | 19 | ```python 20 | class Solution(object): 21 | def containsNearbyAlmostDuplicate(self, nums, k, t): 22 | n = len(nums) 23 | for i in range(n-1): 24 | for j in range(i+1, n): 25 | if abs(nums[i] - nums[j]) <= t and j-i <= k: 26 | return True 27 | return False 28 | ``` 29 | 简单优化依然超时: 30 | 31 | ```python 32 | class Solution(object): 33 | def containsNearbyAlmostDuplicate(self, nums, k, t): 34 | cnt = {} 35 | n = len(nums) 36 | for i in range(n-1): 37 | j = i + 1 38 | while j < n and j < i+k+1: 39 | if abs(nums[i] - nums[j]) <= t: 40 | return True 41 | j += 1 42 | return False 43 | 44 | 45 | ``` 46 | ### 解析2: 47 | 48 | 桶排序,不太明白。 49 | 50 | ```python 51 | class Solution: 52 | def containsNearbyAlmostDuplicate(self, nums, k, t): 53 | if t < 0: return False 54 | n = len(nums) 55 | d = {} 56 | w = t + 1 57 | for i in xrange(n): 58 | m = nums[i] / w 59 | if m in d: 60 | return True 61 | if m - 1 in d and abs(nums[i] - d[m - 1]) < w: 62 | return True 63 | if m + 1 in d and abs(nums[i] - d[m + 1]) < w: 64 | return True 65 | d[m] = nums[i] 66 | if i >= k: del d[nums[i - k] / w] 67 | return False 68 | ``` 69 | -------------------------------------------------------------------------------- /python/221. 最大正方形.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。 3 | ``` 4 | 示例: 5 | 6 | 输入: 7 | 8 | 1 0 1 0 0 9 | 1 0 1 1 1 10 | 1 1 1 1 1 11 | 1 0 0 1 0 12 | 13 | 输出: 4 14 | ``` 15 | 16 | ### 解析1: 17 | 应用动态规划,但是为什么是判断matrix[i-1][j-1]是否是1呢,辅助矩阵的大小不是m* n吗,为什么(m+1)*(n+1)。重新描述下题目。 18 | 19 | 状态含义:dp[i][j] 对应matrix[i-1][j-1]是否,以[i-1,j-1]为左下角的正方形的最大边长。 20 | 转移方程:dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]) + 1 21 | 22 | | |复杂度|大小|百分比| 23 | |--|--|--|--| 24 | |时间|$O(mn)$|184 ms|83.18%| 25 | |空间|$O(mn)$|86.2 MB|26.87%| 26 | 27 | 28 | ```python 29 | class Solution: 30 | def maximalSquare(self, matrix): 31 | if not matrix: return 0 32 | row = len(matrix) 33 | col = len(matrix[0]) 34 | dp = [[0] * (col + 1) for _ in range(row + 1)] 35 | res = 0 36 | for i in range(1, row +1): 37 | for j in range(1, col + 1): 38 | if matrix[i - 1][j - 1] == "1": 39 | dp[i][j] = min(dp[i-1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 40 | res = max(res, dp[i][j]) 41 | return res*res 42 | ``` -------------------------------------------------------------------------------- /python/226.翻转二叉树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 翻转一棵二叉树。 3 | ``` 4 | 示例: 5 | 6 | 输入: 7 | 4 8 | / \ 9 | 2 7 10 | / \ / \ 11 | 1 3 6 9 12 | 13 | 输出: 14 | 15 | 4 16 | / \ 17 | 7 2 18 | / \ / \ 19 | 9 6 3 1 20 | ``` 21 | ### 解析1: 22 | 递归直接翻转,判断树是否为空,如果为空返回None,不为空则翻转左右子树。然后递归调用,对左右子树。 23 | 24 | | |复杂度|大小|百分比| 25 | |--|--|--|--| 26 | |时间|$O(n)$|52 ms|56.52%| 27 | |空间|$O(n)$|13.9 MB|34.60%| 28 | 29 | 30 | ```python 31 | class Solution(object): 32 | def invertTree(self, root): 33 | if root == None: 34 | return None 35 | root.left,root.right = root.right, root.left 36 | self.invertTree(root.left) 37 | self.invertTree(root.right) 38 | return root 39 | ``` 40 | 41 | ### 解析2; 42 | Leetcode的评测时间简直就是随机数啊,太迷了。循环实现,用队列,依次保存树结点。 43 | 44 | | |复杂度|大小|百分比| 45 | |--|--|--|--| 46 | |时间|$O(n)$|40 ms|96.52%| 47 | |空间|$O(n)$|13.9 MB|5.24%| 48 | 49 | ```python 50 | class Solution: 51 | def invertTree(self, root: TreeNode) -> TreeNode: 52 | if not root:return None 53 | queue = [root] 54 | while queue: 55 | temp = queue.pop(0) 56 | 57 | temp.left,temp.right = temp.right,temp.left 58 | if temp.left:queue.append(temp.left) 59 | if temp.right:queue.append(temp.right) 60 | 61 | return root 62 | ``` -------------------------------------------------------------------------------- /python/227. 基本计算器 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 实现一个基本的计算器来计算一个简单的字符串表达式的值。 3 | 4 | 字符串表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格  。 整数除法仅保留整数部分。 5 | ``` 6 | 示例 1: 7 | 8 | 输入: "3+2*2" 9 | 输出: 7 10 | 示例 2: 11 | 12 | 输入: " 3/2 " 13 | 输出: 1 14 | 示例 3: 15 | 16 | 输入: " 3+5 / 2 " 17 | 输出: 5 18 | ``` 19 | 20 | ### 解析1: 21 | -------------------------------------------------------------------------------- /python/229.求众数II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个大小为 n 的数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。 3 | 4 | 说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1)。 5 | 6 | 示例 1: 7 | 8 | 输入: [3,2,3] 9 | 输出: [3] 10 | 11 | ### 解析1: 12 | 依然是统计次数,然后判断是否超过[n/3]。 13 | 14 | ```python 15 | class Solution(object): 16 | def majorityElement(self, nums): 17 | num_cnt = {} 18 | for i in range(len(nums)): 19 | num_cnt[nums[i]] = num_cnt.get(nums[i], 0)+1 20 | res = [] 21 | for key,value in num_cnt.items(): 22 | if value > len(nums)//3: 23 | res.append(key) 24 | return res 25 | ``` -------------------------------------------------------------------------------- /python/230.二叉搜索树中第k小的元素.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。 3 | 4 | 说明: 5 | 你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。 6 | ``` 7 | 示例 1: 8 | 9 | 输入: root = [3,1,4,null,2], k = 1 10 | 3 11 | / \ 12 | 1 4 13 | \ 14 |   2 15 | 输出: 1 16 | ``` 17 | 18 | ### 解析1: 19 | 对二叉搜索树进行中序遍历,中序遍历得到的便是排序数组,然后返回第k个元素。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|52 ms|73.50%| 24 | |空间|$O(n)$|19.7 MB|13.13%| 25 | 26 | 27 | ```python 28 | class Solution(object): 29 | def kthSmallest(self, root, k): 30 | res = [] 31 | def dfs(root): 32 | if not root: 33 | return None 34 | 35 | dfs(root.left) 36 | res.append(root.val) 37 | dfs(root.right) 38 | 39 | dfs(root) 40 | return res[k-1] 41 | ``` -------------------------------------------------------------------------------- /python/231. 2的幂.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数,编写一个函数来判断它是否是 2 的幂次方。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: 1 7 | 输出: true 8 | 解释: 20 = 1 9 | 示例 2: 10 | 11 | 输入: 16 12 | 输出: true 13 | 解释: 24 = 16 14 | ``` 15 | 16 | ### 解析1: 17 | 不断整除2,看最后是否能变成1,对于0和1分开单独判断一下。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(n)$|16ms|96.15%| 22 | |空间|$O(n)$|11.8 MB|10.32%| 23 | 24 | ```python 25 | class Solution(object): 26 | def isPowerOfTwo(self, n): 27 | if n == 0:return False 28 | if n == 1:return True 29 | 30 | while n != 1: 31 | if n % 2 == 0:n = n//2 32 | else:return False 33 | return True 34 | ``` 35 | 36 | ### 解析2: 37 | 数学加幂运算,该用的都用上了。 38 | 39 | ```python 40 | class Solution: 41 | def isPowerOfTwo(self, n: int) -> bool: 42 | return n > 0 and n & (n - 1) == 0 43 | ``` 44 | 45 | ### 解析3: 46 | 统计这个数的二进制表示中,1的个数,如果是2的次幂那么只有一个1。 47 | 48 | ```python 49 | class Solution: 50 | def isPowerOfTwo(self, n: int) -> bool: 51 | if n < 0:return False 52 | return bin(n).count('1') == 1 53 | ``` 54 | -------------------------------------------------------------------------------- /python/234. 回文链表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 请判断一个链表是否为回文链表。 3 | 4 | 示例 1: 5 | 6 | 输入: 1->2 7 | 输出: false 8 | 示例 2: 9 | 10 | 输入: 1->2->2->1 11 | 输出: true 12 | 13 | ### 解析1: 14 | 将链表保存成list,然后判断list是否是回文的。判断首尾是否相等即可。空间复杂度不符合要求。 15 | 16 | | |复杂度|大小|百分比| 17 | |--|--|--|--| 18 | |时间|$O(n)$|84 ms|63.87%| 19 | |空间|$O(n)$|31.5 MB|7.96%| 20 | 21 | 22 | ```python 23 | class Solution(object): 24 | def isPalindrome(self, head): 25 | res = [] 26 | while head: 27 | res.append(head.val) 28 | head = head.next 29 | return self.IsPali(res) 30 | 31 | def IsPali(self, nums): 32 | # return nums == nums[::-1] # 简单写法 33 | n = len(nums) 34 | for i in range(n>>1): 35 | if nums[i] != nums[n-1-i]: 36 | return False 37 | return True 38 | ``` 39 | 40 | ### 解析2: 41 | 利用快慢指针。 42 | 43 | 步骤: 44 | 1. 快慢指针,快指针走两步,慢指针走一步,快指针到末尾,慢指针到中点; 45 | 2. 翻转链表前半部分,或者翻转后半部分,由于获取到了中点结点的指针,所以可以各种比较。 46 | -------------------------------------------------------------------------------- /python/236. 二叉树的最近公共祖先.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 3 | 4 | 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 5 | 6 | ``` 7 | 例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4] 8 | 9 | 3 10 | / \ 11 | 5 1 12 | / \ / \ 13 | 6 2 0 8 14 | / \ 15 | 7 4 16 | ``` 17 | 18 | ### 解析1: 19 | 递归,依次判断结点值是否等于p或者q,维护两个变量left和right,判断p,q是否出现在左右子树中。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|76 ms|99.94%| 24 | |空间|$O(1)$|28.7 MB|5.04%| 25 | 26 | 步骤: 27 | 1. 设置一个辅助函数,求以node为根节点的子树中,是否出现了p或者q; 28 | 2. 如果左子树,右子树和node超过两个为True,他便是最低公共祖先; 29 | 30 | ```python 31 | class Solution: 32 | def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': 33 | self.res = None 34 | def helper(node): 35 | if not node:return False 36 | left = helper(node.left) 37 | right = helper(node.right) 38 | 39 | mid = node == p or node == q 40 | if mid + left + right >= 2: 41 | self.res = node 42 | return mid or left or right 43 | helper(root) 44 | return self.res 45 | ``` -------------------------------------------------------------------------------- /python/237.删除链表中的结点.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。 3 | 4 | 现有一个链表 -- head = [4,5,1,9],它可以表示为: 5 | 6 | ``` 7 | 示例 1: 8 | 9 | 输入: head = [4,5,1,9], node = 5 10 | 输出: [4,1,9] 11 | 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. 12 | ``` 13 | 14 | ### 解析1: 15 | 题目有点迷,跳过当前点既可,val和next做好赋值。建议参考[面试题18. 删除链表的节点](https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/),考察如何删除链表中结点。 16 | 17 | * **复杂度:** 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(1)$|32 ms|69.48%| 21 | |空间|$O(1)$|12.2 MB|34.26%| 22 | 23 | ```python 24 | class Solution(object): 25 | def deleteNode(self, node): 26 | node.val = node.next.val 27 | node.next = node.next.next 28 | ``` -------------------------------------------------------------------------------- /python/238. 除自身以外数组的乘积.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 3 | 给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。 4 | 5 | >示例: 6 | 输入: [1,2,3,4] 7 | 输出: [24,12,8,6] 8 | 9 | ### 解析1: 10 | 暴力,遍历两次数组。 11 | 12 | | |复杂度|大小|百分比| 13 | |--|--|--|--| 14 | |时间|$O(n^2)$|超时| | 15 | |空间|$O(1)$| | | 16 | 17 | ```python 18 | class Solution(object): 19 | def productExceptSelf(self, nums): 20 | res = [] 21 | for i in range(len(nums)): 22 | temp = 1 23 | for j in range(len(nums)): 24 | if i == j:continue 25 | temp *= nums[j] 26 | res.append(temp) 27 | return res 28 | ``` 29 | 30 | ### 解析2: 31 | 遍历两次数组,第一次正向累乘,将第k个数字前面的所有数字相乘保存在第k个数里面。反向相乘,将第k个数后面的累乘。 32 | 33 | | |复杂度|大小|百分比| 34 | |--|--|--|--| 35 | |时间|$O(n)$|196 ms|21.20%| 36 | |空间|$O(1)$|18.5 MB|56.69% | 37 | 38 | 注意累乘的起始点,第一个和最后一个数不算在累乘里面。 39 | 40 | ```python 41 | class Solution(object): 42 | def productExceptSelf(self, nums): 43 | length = len(nums) 44 | res = [1]*length 45 | for i in range(1, length): 46 | res[i] = nums[i-1]*res[i-1] 47 | temp = 1 48 | for i in range(length-2, -1,-1): 49 | temp *= nums[i+1] 50 | res[i] = temp * res[i] 51 | return res 52 | ``` -------------------------------------------------------------------------------- /python/239. 滑动窗口最大值.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 3 | 4 | 返回滑动窗口中的最大值。 5 | 6 | ``` 7 | 示例: 8 | 9 | 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 10 | 输出: [3,3,5,5,6,7] 11 | ``` 12 | 13 | ### 解析1: 14 | 暴力法,滑动窗口遍历,每次求解窗口的最大值。 15 | 16 | | |复杂度|大小|百分比| 17 | |--|--|--|--| 18 | |时间|$O(nk)$|620 ms|17.10%| 19 | |空间|$O(n-k)$|18.8 MB|27.66%| 20 | 21 | ```python 22 | class Solution(object): 23 | def maxSlidingWindow(self, nums, k): 24 | n = len(nums) 25 | if n*k == 0: 26 | return [] 27 | 28 | return [max(nums[i:i+k]) for i in range(n-k+1)] 29 | ``` -------------------------------------------------------------------------------- /python/240. 搜索二维矩阵II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性: 3 | 4 | 每行的元素从左到右升序排列。 5 | 每列的元素从上到下升序排列。 6 | ``` 7 | 示例: 8 | 9 | 现有矩阵 matrix 如下: 10 | 11 | [ 12 | [1, 4, 7, 11, 15], 13 | [2, 5, 8, 12, 19], 14 | [3, 6, 9, 16, 22], 15 | [10, 13, 14, 17, 24], 16 | [18, 21, 23, 26, 30] 17 | ] 18 | 给定 target = 5,返回 true。 19 | 20 | 给定 target = 20,返回 false。 21 | ``` 22 | 23 | ### 解析1: 24 | 原矩阵从左到右升序;从上到下升序。从右上到左下遍历。暴力遍历的方法简单在此不表。 25 | 26 | | |复杂度|大小|百分比| 27 | |--|--|--|--| 28 | |时间|$O(max(n,m))$|56 ms|86.65%| 29 | |空间|$O(1)$|18.5 MB|5.18%| 30 | 31 | ```python 32 | class Solution: 33 | def searchMatrix(self, matrix, target): 34 | if not matrix or not matrix[0]:return False 35 | n,m = len(matrix),len(matrix[0]) 36 | i,j = 0,m-1 37 | while i < n and j >= 0: 38 | if matrix[i][j] == target:return True 39 | elif matrix[i][j] > target: j -= 1 40 | else: i += 1 41 | return False 42 | ``` -------------------------------------------------------------------------------- /python/242. 有效的字母异位词.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: s = "anagram", t = "nagaram" 7 | 输出: true 8 | 示例 2: 9 | 10 | 输入: s = "rat", t = "car" 11 | 输出: false 12 | ``` 13 | 14 | ### 解析1: 15 | 统计每个字符出现的次数,然后判断两个哈希表是否相等。 16 | 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(logn)$|36 ms|100%| 20 | |空间|$O(n)$|14.1 MB|22.38%| 21 | 22 | ```python 23 | from collections import Counter 24 | class Solution: 25 | def isAnagram(self, s: str, t: str) -> bool: 26 | return Counter(s) == Counter(t) 27 | ``` -------------------------------------------------------------------------------- /python/26. 删除排序数组中的重复项.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 3 | 4 | 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 5 | ``` 6 | 示例 2: 7 | 8 | 给定 nums = [0,0,1,1,1,2,2,3,3,4], 9 | 10 | 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 11 | 12 | 你不需要考虑数组中超出新长度后面的元素。 13 | ``` 14 | 15 | ### 解析1: 16 | 双指针,一个在前面遍历,一个代表有多少个不重复的数。 17 | 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(n)$|76 ms|94.49%| 21 | |空间|$O(n)$|13.6 MB|25.63%| 22 | 23 | ```python 24 | class Solution(object): 25 | def removeDuplicates(self, nums): 26 | res = 0 27 | for i in range(1,len(nums)): 28 | if nums[i] != nums[i-1]: 29 | res += 1 30 | nums[res] = nums[i] 31 | return res+1 32 | ``` -------------------------------------------------------------------------------- /python/260. 只出现一次的数字 III.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。 3 | ``` 4 | 示例 : 5 | 6 | 输入: [1,2,1,3,2,5] 7 | 输出: [3,5] 8 | ``` 9 | 10 | ### 解析1: 11 | 同样的套路,我佛了。 12 | 13 | | |复杂度|大小|百分比| 14 | |--|--|--|--| 15 | |时间|$O(n)$|60ms|75.9%| 16 | |空间|$O(n)$|14.7 MB|5.32%| 17 | 18 | 19 | ```python 20 | class Solution(object): 21 | def singleNumber(self, nums): 22 | cnt_dict = {} 23 | res = [] 24 | for num in nums: 25 | cnt_dict[num] = cnt_dict.get(num, 0) + 1 26 | for key,value in cnt_dict.items(): 27 | if value == 1: 28 | res.append(key) 29 | return res 30 | ``` 31 | 32 | 再奉上一套。 33 | 34 | | |复杂度|大小|百分比| 35 | |--|--|--|--| 36 | |时间|$O(n)$|68ms|45.78| 37 | |空间|$O(n)$|13.5 MB|15.32%| 38 | 39 | ```python 40 | class Solution(object): 41 | def singleNumber(self, nums): 42 | num_set = set() 43 | for num in nums: 44 | if num not in num_set: 45 | num_set.add(num) 46 | else: 47 | num_set.remove(num) 48 | return list(num_set) 49 | ``` -------------------------------------------------------------------------------- /python/263. 丑数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 编写一个程序判断给定的数是否为丑数。 3 | 4 | 丑数就是只包含质因数 2, 3, 5 的正整数。 5 | 6 | 示例 1: 7 | 8 | 输入: 6 9 | 输出: true 10 | 解释: 6 = 2 × 3 11 | 示例 2: 12 | 13 | 输入: 8 14 | 输出: true 15 | 解释: 8 = 2 × 2 × 2 16 | 示例 3: 17 | 18 | 输入: 14 19 | 输出: false 20 | 21 | 22 | ### 解析1: 23 | **算法流程:** 24 | 1. 不断除以2,3,5,判断是是否还有因子2,3,5; 25 | 2. 如果不能除以2,3,5,且不为1,则False,否则True 26 | 27 | **复杂度:** 28 | | |复杂度|大小|百分比| 29 | |--|--|--|--| 30 | |时间|$O( )$|60 ms|9.76%| 31 | |空间|$O(1)$|13.7 MB|5.31%| 32 | 33 | ```python 34 | class Solution: 35 | def isUgly(self, num: int) -> bool: 36 | if num <= 0:return False 37 | while num not in set([1,2,3,5]): 38 | if num %2 == 0:num /= 2 39 | elif num % 3 == 0:num //= 3 40 | elif num % 5 == 0:num //= 5 41 | else:return False 42 | return True 43 | ``` 44 | 45 | 进行一次优化,循环中如果num为1,返回True,如果因子没有2,3,5且不为1,那么返回False。 46 | 47 | ```python 48 | class Solution: 49 | def isUgly(self, num: int) -> bool: 50 | if num <= 0: 51 | return False 52 | while True: 53 | if num %2 == 0: 54 | num /= 2 55 | elif num % 3 == 0: 56 | num /= 3 57 | elif num % 5 == 0: 58 | num /= 5 59 | elif num == 1: 60 | return True 61 | else: 62 | return False 63 | ``` -------------------------------------------------------------------------------- /python/268. 缺失数字.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 3 | 给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。 4 | ``` 5 | 示例 1: 6 | 7 | 输入: [3,0,1] 8 | 输出: 2 9 | ``` 10 | 11 | ### 解析1: 12 | 数学法,确实的数字 = sum[0,1,2,..n] - sum(nums)。排序,哈希表等方法过于麻烦在此不表。 13 | 14 | | |复杂度|大小|百分比| 15 | |--|--|--|--| 16 | |时间|$O(n)$|148ms|94.26%| 17 | |空间|$O(1)$|12.7 MB|13.35%| 18 | 19 | ```python 20 | class Solution(object): 21 | def missingNumber(self, nums): 22 | n = len(nums) 23 | return n*(n+1)/2 - sum(nums) 24 | ``` -------------------------------------------------------------------------------- /python/279. 完全平方数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: n = 12 7 | 输出: 3 8 | 解释: 12 = 4 + 4 + 4. 9 | 示例 2: 10 | 11 | 输入: n = 13 12 | 输出: 2 13 | 解释: 13 = 4 + 9. 14 | ``` 15 | 16 | ### 解析1: 17 | 动态规划,但是超时了。 18 | 19 | 状态含义:dp[i]和为i的最小完全平方数个数; 20 | 状态方程:dp[i] = min(dp[i], dp[i-num]+1) for num in nums 21 | nums为小于的i的完全平方数数组集合。 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(n^2)$|超时|| 26 | |空间|$O(n)$||| 27 | 28 | 29 | ```python 30 | class Solution: 31 | def numSquares(self, n: int) -> int: 32 | squares = [] 33 | for i in range(1,n): 34 | if i **2<=n: 35 | squares.append(i**2) 36 | dp = [n]*(n+1) 37 | dp[0] = 0 38 | for i in range(1,n+1): 39 | for num in squares: 40 | if i >= num: 41 | dp[i] = min(dp[i], dp[i-num]+1) 42 | else: 43 | break 44 | return dp[n] 45 | ``` 46 | 47 | ### 解析2: 48 | 拉格朗日四平方数公式。 49 | 50 | ```python 51 | class Solution: 52 | def numSquares(self, n: int) -> int: 53 | while n % 4 == 0: 54 | n /= 4 55 | if n % 8 == 7: 56 | return 4 57 | 58 | a = 0 59 | while a**2 <= n: 60 | b = int((n - a**2)**0.5) 61 | if a**2 + b**2 == n: 62 | return bool(a) + bool(b) 63 | a += 1 64 | 65 | return 3 66 | ``` -------------------------------------------------------------------------------- /python/28. 实现 strStr().md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 实现 strStr() 函数。 3 | 4 | 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。 5 | ``` 6 | 示例 1: 7 | 8 | 输入: haystack = "hello", needle = "ll" 9 | 输出: 2 10 | 示例 2: 11 | 12 | 输入: haystack = "aaaaa", needle = "bba" 13 | 输出: -1 14 | ``` 15 | 16 | ### 解析1: 17 | 遍历,然后判断窗口元素是否和needle相同。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(mn)$|40 ms|96.50%| 22 | |空间|$O(1)$|14 MB|5.73%| 23 | 24 | ```python 25 | class Solution: 26 | def strStr(self, haystack: str, needle: str) -> int: 27 | n1,n2 = len(haystack),len(needle) 28 | for i in range(n1 - n2 + 1): 29 | if haystack[i:i+n2] == needle: 30 | return i 31 | return -1 32 | ``` 33 | 34 | ### 解析2: 35 | KMP算法实现,整个算法理解起来还是比较麻烦的。 36 | 37 | ```python 38 | class Solution: 39 | def strStr(self, t, p): 40 | if not p : return 0 41 | _next = [0] * len(p) 42 | 43 | def getNext(p, _next): 44 | _next[0] = -1 45 | i = 0 46 | j = -1 47 | while i < len(p) - 1: 48 | if j == -1 or p[i] == p[j]: 49 | i += 1 50 | j += 1 51 | _next[i] = j 52 | else: 53 | j = _next[j] 54 | getNext(p, _next) 55 | i = 0 56 | j = 0 57 | while i < len(t) and j < len(p): 58 | if j == -1 or t[i] == p[j]: 59 | i += 1 60 | j += 1 61 | else: 62 | j = _next[j] 63 | if j == len(p): 64 | return i - j 65 | return -1 66 | ``` -------------------------------------------------------------------------------- /python/283. 移动零.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 3 | 4 | 示例: 5 | 6 | 输入: [0,1,0,3,12] 7 | 输出: [1,3,12,0,0] 8 | 说明: 9 | 10 | 必须在原数组上操作,不能拷贝额外的数组。 11 | 尽量减少操作次数。 12 | 13 | ### 解析1: 14 | 遇到0,remove,然后append。 15 | 16 | | |复杂度|大小|百分比| 17 | |--|--|--|--| 18 | |时间|$O(n)$|84 ms|63.87%| 19 | |空间|$O(n)$|31.5 MB|7.96%| 20 | 21 | ```python 22 | class Solution(object): 23 | def moveZeroes(self, nums): 24 | n = len(nums) 25 | for i in range(n): 26 | if nums[i] == 0: 27 | nums.remove(0) 28 | nums.append(0) 29 | return nums 30 | ``` 31 | 32 | ### 解析2: 33 | 遇到0依次向后排,如果遇到元素不是0,就和第一个0替换,将0替换到后面。快慢指针的思想。快指针依次遍历,数组,碰到不是0的元素就和慢指针替换。 34 | 35 | | |复杂度|大小|百分比| 36 | |--|--|--|--| 37 | |时间|$O(n)$|32 ms|99.64%| 38 | |空间|$O(1)$|12.7 MB|33.48%| 39 | 40 | 41 | ```python 42 | class Solution(object): 43 | def moveZeroes(self, nums): 44 | j = 0 45 | for i in range(len(nums)): 46 | # j保存第一个0的位置,每次遇到不是0的元素,和第一个0进行替换 47 | if nums[i] != 0: 48 | nums[i], nums[j] = nums[j],nums[i] 49 | j += 1 50 | ``` 51 | 52 | ### 解析3: 53 | 直接调用函数一行实现,很迷不知道为什么用sorted不行。 54 | 55 | ```python 56 | class Solution(object): 57 | def moveZeroes(self, nums): 58 | # nums = sorted(nums, key=bool, reverse=True) 59 | nums.sort(key=bool, reverse=True) 60 | ``` 61 | -------------------------------------------------------------------------------- /python/289.生命游戏.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 根据百度百科,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在1970年发明的细胞自动机。 3 | 4 | 给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞具有一个初始状态 live(1)即为活细胞, 或 dead(0)即为死细胞。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律: 5 | 6 | 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡; 7 | 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活; 8 | 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡; 9 | 如果死细胞周围正好有三个活细胞,则该位置死细胞复活; 10 | 根据当前状态,写一个函数来计算面板上细胞的下一个(一次更新后的)状态。下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。 11 | ``` 12 | 示例: 13 | 14 | 输入: 15 | [ 16 |   [0,1,0], 17 |   [0,0,1], 18 |   [1,1,1], 19 |   [0,0,0] 20 | ] 21 | 输出: 22 | [ 23 |   [0,0,0], 24 |   [1,0,1], 25 |   [0,1,1], 26 |   [0,1,0] 27 | ] 28 | ``` 29 | 30 | ### 解析1: 31 | 按照题意,进行判断比较。 32 | 33 | ```python 34 | class Solution(object): 35 | def gameOfLife(self, board): 36 | if not board:return [] 37 | m,n = len(board),len(board[0]) 38 | 39 | flag = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]] 40 | res = [[0]*n for _ in range(m)] 41 | for i in range(m): 42 | temp = [] 43 | for j in range(n): 44 | live = 0 45 | for x,y in flag: 46 | n_i = i + x 47 | n_j = j + y 48 | if 0 <= n_i < m and 0 <= n_j < n and board[n_i][n_j] == 1: 49 | live += 1 50 | 51 | if board[i][j] == 1: 52 | if live == 2 or live == 3:res[i][j] = 1 53 | else:res[i][j] = 0 54 | else: 55 | if live == 3:res[i][j] = 1 56 | else:res[i][j] = 0 57 | for i in range(m): 58 | for j in range(n): 59 | board[i][j] = res[i][j] 60 | ``` -------------------------------------------------------------------------------- /python/29. 两数相除.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。 3 | 4 | 返回被除数 dividend 除以除数 divisor 得到的商。 5 | ``` 6 | 示例 1: 7 | 8 | 输入: dividend = 10, divisor = 3 9 | 输出: 3 10 | 示例 2: 11 | 12 | 输入: dividend = 7, divisor = -3 13 | 输出: -2 14 | ``` 15 | 16 | ### 解析1: 17 | 作弊玩法: 18 | 19 | ```python 20 | class Solution: 21 | def divide(self, dividend: int, divisor: int) -> int: 22 | flag = 1 if dividend ^ divisor >= 0 else -1 23 | res = abs(dividend)//abs(divisor) 24 | res *= flag 25 | return min(max(-2**31, res), 2**31-1) 26 | ``` 27 | 28 | ### 解析2: 29 | 被除数依次减去除数,但是次数太多的时候,会超时。 30 | 31 | 32 | ```python 33 | class Solution: 34 | def divide(self, dividend: int, divisor: int) -> int: 35 | flag = 1 if dividend ^ divisor >= 0 else -1 36 | res = 0 37 | dividend,divisor = abs(dividend),abs(divisor) 38 | while dividend >= divisor: 39 | res += 1 40 | dividend -= divisor 41 | 42 | if flag < 0:res = -res 43 | if -2**31 <= res <= 2**31 - 1:return res 44 | else:return 2**31-1 45 | ``` -------------------------------------------------------------------------------- /python/292. Nim 游戏.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。 3 | 4 | 你们是聪明人,每一步都是最优解。 编写一个函数,来判断你是否可以在给定石头数量的情况下赢得游戏。 5 | ``` 6 | 示例: 7 | 8 | 输入: 4 9 | 输出: false 10 | 解释: 如果堆中有 4 块石头,那么你永远不会赢得比赛; 11 |   因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走。 12 | ``` 13 | 14 | ### 解析1: 15 | 寻找规律,1,2,3先手肯定会赢,4的时候不管取多少个都会输,5,6,7都会赢。 16 | 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(1)$|40 ms|94.59%| 20 | |空间|$O(1)$|13.9 MB|5.37%| 21 | 22 | ```python 23 | class Solution: 24 | def canWinNim(self, n: int) -> bool: 25 | return n%4 != 0 26 | ``` -------------------------------------------------------------------------------- /python/31. 下一个排列.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。 3 | 4 | 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。 5 | 6 | 必须原地修改,只允许使用额外常数空间。 7 | 8 | 以下是一些例子,输入位于左侧列,其相应输出位于右侧列。 9 | 1,2,3 → 1,3,2 10 | 3,2,1 → 1,2,3 11 | 1,1,5 → 1,5,1 12 | 13 | 14 | ### 解析1: 15 | 16 | 固定类型题目,记住算法: 17 | 1. 找出最大满足 nums[k] < nums[k+1]的索引,如果不存在,就翻转整个数组; 18 | 2. 找出另一个最大索引l满足 nums[l] > nums[k]; 19 | 3. 交换 nums[l] 和 nums[k] 20 | 4. 翻转 nums[k+1:] 21 | 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(n)$|72 ms|31.56%| 26 | |空间|$O(1)$|13.7 MB|5.12%| 27 | 28 | 29 | ```python 30 | class Solution: 31 | def nextPermutation(self, nums): 32 | firstIndex = -1 33 | n = len(nums) 34 | for i in range(n-2, -1, -1): 35 | if nums[i] < nums[i+1]: 36 | firstIndex = i 37 | break 38 | #print(firstIndex) 39 | if firstIndex == -1: 40 | nums = nums[::-1] 41 | return 42 | secondIndex = -1 43 | for i in range(n-1, firstIndex, -1): 44 | if nums[i] > nums[firstIndex]: 45 | secondIndex = i 46 | break 47 | nums[firstIndex],nums[secondIndex] = nums[secondIndex], nums[firstIndex] 48 | nums[firstIndex+1:] = nums[firstIndex+1:][::-1] 49 | ``` -------------------------------------------------------------------------------- /python/312. 戳气球.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。 3 | 4 | 现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。 5 | 6 | 求所能获得硬币的最大数量。 7 | 8 | 说明: 9 | 10 | 你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。 11 | 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100 12 | ``` 13 | 示例: 14 | 15 | 输入: [3,1,5,8] 16 | 输出: 167 17 | 解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] 18 |   coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167 19 | ``` 20 | 21 | ### 解析1: 22 | 动态规划,但是状态转移方程比较难得到,所以重点是状态转移方程的推导。 23 | 24 | 状态含义:dp[i][j] 戳破[i+1,...j-1]号气球的最大收益; 25 | 状态转移方程:dp[i][j] = max(nums[i] * nums[k] * nums[j] + dp[i][k]+dp[k][j]) for k in range(i+1,j) 26 | ,假设k为这一段最后一个戳破的。 27 | 28 | 讲解这个状态方程是如何得到的: 29 | 30 | 假设数组是[1,2,3,4,5,6,7,8],假设i=0,j=7,dp[0][7]表示[2,3,4,5,6,7]这段的最大值,假设k为3,则将这两段划分成[2,3]和[5,6,7],那么在k之前,剩下的便是nums[i],nums[k]和nums[j],所以最后是nums[i]*nums[k] *nums[k],k之前的也需要考虑进去,即dp[i][k]和dp[k][j],所以产生了上述的状态转移方程。 31 | 32 | ```python 33 | class Solution(object): 34 | def maxCoins(self, nums): 35 | nums = [1] + nums + [1] # build the complete array 36 | n = len(nums) 37 | dp = [[0] * n for _ in range(n)] 38 | 39 | for gap in range(2, n): 40 | for i in range(n-gap): 41 | j = i + gap 42 | for k in range(i+1, j): 43 | dp[i][j] = max(dp[i][j], nums[i] * nums[k] * nums[j] + dp[i][k] + dp[k][j]) 44 | return dp[0][n-1] 45 | ``` -------------------------------------------------------------------------------- /python/315. 计算右侧小于当前元素的个数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量。 3 | ``` 4 | 示例: 5 | 6 | 输入: [5,2,6,1] 7 | 输出: [2,1,1,0] 8 | 解释: 9 | 5 的右侧有 2 个更小的元素 (2 和 1). 10 | 2 的右侧仅有 1 个更小的元素 (1). 11 | 6 的右侧有 1 个更小的元素 (1). 12 | 1 的右侧有 0 个更小的元素. 13 | ``` 14 | 15 | ### 解析1: 16 | 暴力法,遍历当前数后面的数,求小于当前树的个数,明显超时。 17 | 18 | 19 | ```python 20 | class Solution(object): 21 | def countSmaller(self, nums): 22 | res = [] 23 | n = len(nums) 24 | for i in range(n): 25 | temp = 0 26 | for j in range(i+1, n): 27 | if nums[j] < nums[i]:temp += 1 28 | res.append(temp) 29 | return res 30 | ``` -------------------------------------------------------------------------------- /python/322. 零钱兑换.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 3 | 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 4 | ``` 5 | 示例 1: 6 | 7 | 输入: coins = [1, 2, 5], amount = 11 8 | 输出: 3 9 | 解释: 11 = 5 + 5 + 1 10 | 示例 2: 11 | 12 | 输入: coins = [2], amount = 3 13 | 输出: -1 14 | ``` 15 | 16 | ### 解析1: 17 | 动态规划。 18 | 19 | 状态含义:dp[i]凑成金额i所需的最少的硬币个数; 20 | 转移方程:dp[i] = min(dp[i], dp[i-coin]+1) for coin in coins if i >= coin 21 | 22 | 即遍历硬币组合;总金额amount的所需硬币个数即,去掉某个硬币值后所需个数dp[i-coin]+1。遍历所有情况,取最小值即可。 23 | 24 | | |复杂度|大小|百分比| 25 | |--|--|--|--| 26 | |时间|$O(n)$|2256 ms|34.46%| 27 | |空间|$O(n)$|13.7 MB|14.79%| 28 | 29 | ```python 30 | class Solution: 31 | def coinChange(self, coins: List[int], amount: int) -> int: 32 | dp = [amount+1]*(amount+1) 33 | n = len(coins) 34 | dp[0] = 0 35 | for i in range(1, amount+1): 36 | for j in range(n): 37 | if i >= coins[j]: 38 | dp[i] = min(dp[i], dp[i-coins[j]]+1) 39 | return -1 if dp[amount] > amount else dp[amount] 40 | ``` -------------------------------------------------------------------------------- /python/326. 3的幂.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数,写一个函数来判断它是否是 3 的幂次方。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: 27 7 | 输出: true 8 | ``` 9 | 10 | ### 解析1: 11 | 直接循环判断,没啥好说的。 12 | 13 | | |复杂度|大小|百分比| 14 | |--|--|--|--| 15 | |时间|$O(n)$|140 ms|36.93%| 16 | |空间|$O(n)$|14 MB|5.64%| 17 | 18 | 19 | ```python 20 | class Solution: 21 | def isPowerOfThree(self, n: int) -> bool: 22 | if n==0:return False 23 | while n!=1: 24 | if n%3 == 0:n = n//3 25 | else:return False 26 | return True 27 | ``` -------------------------------------------------------------------------------- /python/328. 奇偶链表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。 3 | 4 | 请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。 5 | ``` 6 | 示例 1: 7 | 8 | 输入: 1->2->3->4->5->NULL 9 | 输出: 1->3->5->2->4->NULL 10 | ``` 11 | 12 | ### 解析1: 13 | 设置奇偶指针,偶数指针单独列出得到一个链表,将链表中的奇偶元素单独提取出来。 14 | 15 | | |复杂度|大小|百分比| 16 | |--|--|--|--| 17 | |时间|$O(n)$|76 ms|99.94%| 18 | |空间|$O(1)$|28.7 MB|5.04%| 19 | 20 | 21 | ```python 22 | class Solution(object): 23 | def oddEvenList(self, head): 24 | if not head:return None 25 | res = odd = head 26 | even = evenHead = head.next 27 | while even and even.next: 28 | odd.next = even.next 29 | odd = odd.next 30 | 31 | even.next = odd.next 32 | even = even.next 33 | odd.next = evenHead 34 | return res 35 | ``` -------------------------------------------------------------------------------- /python/33. 搜索旋转排序数组.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 3 | 4 | ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 5 | 6 | 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。 7 | 8 | 你可以假设数组中不存在重复的元素。 9 | 10 | 你的算法时间复杂度必须是 O(log n) 级别。 11 | 12 | >示例 1: 13 | 输入: nums = [4,5,6,7,0,1,2], target = 0 14 | 输出: 4 15 | 16 | ### 解析1: 17 | 遍历数组。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(n)$|48 ms|21.59%| 22 | |空间|$O(1)$|12.1 MB|5.1%| 23 | 24 | 25 | ```python 26 | class Solution(object): 27 | def search(self, nums, target): 28 | for i in range(len(nums)): 29 | if nums[i] == target: 30 | return i 31 | return -1 32 | ``` 33 | 34 | ### 35 | 36 | 37 | 38 | 39 | 40 | 41 | ### 二分法: 42 | 根本思想是二分法,首先看一下典型的二分查找。对于一个有序数组,查找某个元素在数组中的位置,如果找不到返回-1。 43 | ```python 44 | def binary_search(inp, item): 45 | left,right = 0,len(inp) 46 | while left <=right: 47 | mid = (left + right)//2 48 | if inp[mid] == item: 49 | return mid 50 | elif inp[mid] > item: 51 | # 如果不相等判断下一个值即可 52 | right = mid-1 53 | else: 54 | left = mid + 1 55 | return -1 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /python/337. 打家劫舍 III.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。 3 | 4 | 计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。 5 | 6 | >示例 1: 7 | 输入: [3,2,3,null,3,null,1] 8 | 9 | 3 10 | / \ 11 | 2 3 12 | \ \ 13 | 3 1 14 | 15 | 输出: 7 16 | 解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7. 17 | 18 | ### 解析1: 19 | 动态规划还是不太懂。DFS函数返回一个数组,第一位代表不包括当前结点值的最大值,第二位代表包括当前结点的最大值。对树进行前序遍历,遍历到node结点,如果不选node结点,则其对应的最大值为max(l)+max(r)。如果选这个结点,则对应的最大值为cur.val+l[0]+r[0]。 20 | 21 | 最后返回根节点,DFS返回数组的最大值即可,即包括根节点与不包括根结点的最大值。 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(n)$|44 ms|80.30%| 26 | |空间|$O(1)$|15.9 MB|19.35%| 27 | 28 | 29 | ```python 30 | class Solution: 31 | def DFS(self , cur): 32 | if not cur : 33 | return [0,0] 34 | 35 | l = self.DFS(cur.left) 36 | r = self.DFS(cur.right) 37 | 38 | return [max(l)+max(r),cur.val+l[0]+r[0]] 39 | def rob(self, root): 40 | return max(self.DFS(root)) 41 | ``` -------------------------------------------------------------------------------- /python/338. 比特位计数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: 2 8 | 输出: [0,1,1] 9 | 示例 2: 10 | 11 | 输入: 5 12 | 输出: [0,1,1,2,1,2] 13 | ``` 14 | 15 | ### 解析1: 16 | 和191完全类似,遍历求解每个数二进制表示中1的个数。 17 | 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(n)$|96 ms|41.38%| 21 | |空间|$O(n)$|15.7 MB|38.16%| 22 | 23 | ```python 24 | class Solution(object): 25 | def countBits(self, num): 26 | res = [] 27 | for i in range(num+1): 28 | res.append(bin(i).count('1')) 29 | return res 30 | ``` -------------------------------------------------------------------------------- /python/343. 整数拆分.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: 2 7 | 输出: 1 8 | 解释: 2 = 1 + 1, 1 × 1 = 1。 9 | ``` 10 | 11 | ### 解析1: 12 | 贪心法:将绳子划分成更多的段,并且没有1,乘积会最大 13 | * 尽可能找到更多的3; 14 | * 没有3了找到尽可能多的2; 15 | * 然后得到1 16 | 17 | * **复杂度:** 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(1)$|40 ms|48.91%| 21 | |空间|$O(1)$|13.4 MB|100%| 22 | 23 | ```python 24 | class Solution: 25 | def integerBreak(self, n: int) -> int: 26 | if n <= 3:return n-1 27 | a,b = n%3,n//3 28 | if a == 0:return 3**b 29 | elif a == 1:return 3**(b-1)*4 30 | else:return 3**b*2 31 | ``` -------------------------------------------------------------------------------- /python/344. 反转字符串.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 3 | 4 | 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 5 | 6 | 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 7 | 8 | ``` 9 | 示例 1: 10 | 11 | 输入:["h","e","l","l","o"] 12 | 输出:["o","l","l","e","h"] 13 | ``` 14 | 15 | ### 解析1: 16 | 主要是原地翻转。 17 | 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(n)$|340 ms|73.50%| 21 | |空间|$O(1)$|18.5 MB|95.55%| 22 | 23 | ```python 24 | class Solution(object): 25 | def reverseString(self, s): 26 | s[0::]=s[::-1] 27 | ``` -------------------------------------------------------------------------------- /python/350. 两个数组的交集 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定两个数组,编写一个函数来计算它们的交集。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: nums1 = [1,2,2,1], nums2 = [2,2] 7 | 输出: [2,2] 8 | 示例 2: 9 | 10 | 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 11 | 输出: [4,9] 12 | ``` 13 | ### 解析1: 14 | 暴力法,依次判断是否出现过,需要注意重复词。 15 | 16 | | |复杂度|大小|百分比| 17 | |--|--|--|--| 18 | |时间|$O(n×m)$|56 ms|53.04%| 19 | |空间|$O(n)$|11.8 MB|31.44%| 20 | 21 | ```python 22 | class Solution(object): 23 | def intersect(self, nums1, nums2): 24 | res = [] 25 | for i in range(len(nums1)): 26 | if nums1[i] in nums2: 27 | res.append(nums1[i]) 28 | nums2.remove(nums1[i]) 29 | return res 30 | ``` 31 | 32 | ### 解析2: 33 | 使用哈希表来实现。Counter统计每个数组的次数,然后取与,取与会得到共有的key同时选择value较小的值。 34 | 35 | | |复杂度|大小|百分比| 36 | |--|--|--|--| 37 | |时间|$O(n,m)$|56 ms|94.04%| 38 | |空间|$O(n)$|11.8 MB|31.44%| 39 | 40 | ```python 41 | from collections import Counter 42 | class Solution(object): 43 | def intersect(self, nums1, nums2): 44 | return [*(collections.Counter(nums1) & collections.Counter(nums2)).elements()] 45 | ``` 46 | 47 | ## 解析3: 48 | 排序,排序后进行比较,如果相等,则拼接,根据大小关系调整ij的索引。 49 | 50 | | |复杂度|大小|百分比| 51 | |--|--|--|--| 52 | |时间|$O(nlog(n),mlog(m))$|56 ms|94.04%| 53 | |空间|$O(n)$|13.9 MB|5.06%| 54 | 55 | ```python 56 | class Solution(object): 57 | def intersect(self, nums1, nums2): 58 | nums1 = sorted(nums1) 59 | nums2 = sorted(nums2) 60 | i = j =0 61 | res = [] 62 | while i < len(nums1) and j < len(nums2): 63 | if nums1[i] == nums2[j]: 64 | res.append(nums1[i]) 65 | i += 1 66 | j += 1 67 | elif nums1[i] < nums2[j]: 68 | i += 1 69 | elif nums1[i] > nums2[j]: 70 | j += 1 71 | return res 72 | ``` -------------------------------------------------------------------------------- /python/36. 有效的数独.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。 3 | 4 | 数字 1-9 在每一行只能出现一次。 5 | 数字 1-9 在每一列只能出现一次。 6 | 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 7 | ``` 8 | 示例 1: 9 | 10 | 输入: 11 | [ 12 | ["5","3",".",".","7",".",".",".","."], 13 | ["6",".",".","1","9","5",".",".","."], 14 | [".","9","8",".",".",".",".","6","."], 15 | ["8",".",".",".","6",".",".",".","3"], 16 | ["4",".",".","8",".","3",".",".","1"], 17 | ["7",".",".",".","2",".",".",".","6"], 18 | [".","6",".",".",".",".","2","8","."], 19 | [".",".",".","4","1","9",".",".","5"], 20 | [".",".",".",".","8",".",".","7","9"] 21 | ] 22 | 输出: true 23 | ``` 24 | 25 | ### 解析1: 26 | 按照定义解决。 27 | 28 | | |复杂度|大小|百分比| 29 | |--|--|--|--| 30 | |时间|$O(1)$|112 ms|82.10%| 31 | |空间|$O(1)$|13.8 MB|5.17%| 32 | 33 | ```python 34 | class Solution: 35 | def isValidSudoku(self, board: List[List[str]]) -> bool: 36 | for i in range(9): 37 | temp = set() 38 | for j in range(9): 39 | if board[i][j] in temp:return False 40 | if board[i][j].isdigit():temp.add(board[i][j]) 41 | 42 | for i in range(9): 43 | temp = set() 44 | for j in range(9): 45 | if board[j][i] in temp:return False 46 | if board[j][i].isdigit():temp.add(board[j][i]) 47 | 48 | array1 = [(0,0),(0,3),(0,6),(3,0),(3,3),(3,6),(6,0),(6,3),(6,6)] 49 | flag = [(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)] 50 | 51 | for i,j in array1: 52 | temp = set() 53 | for x,y in flag: 54 | n_x = i+x 55 | n_y = j+y 56 | 57 | if board[n_x][n_y] in temp:return False 58 | if board[n_x][n_y].isdigit():temp.add(board[n_x][n_y]) 59 | return True 60 | ``` -------------------------------------------------------------------------------- /python/371. 两整数之和.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 不使用运算符 + 和 - ​​​​​​​,计算两整数 ​​​​​​​a 、b ​​​​​​​之和。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: a = 1, b = 2 7 | 输出: 3 8 | 示例 2: 9 | 10 | 输入: a = -2, b = 3 11 | 输出: 1 12 | ``` 13 | 14 | ### 解析1: 15 | emmm有点迷,直接相加吧。 16 | 17 | ```python 18 | class Solution: 19 | def getSum(self, a: int, b: int) -> int: 20 | return a+b 21 | 22 | 23 | ``` 24 | ### 解析2: 25 | 二进制,模拟进位操作,异或和与操作。因为python中整数不是32位,所以需要模拟32位操作,进行数值上的调整。 26 | 27 | 28 | ```python 29 | class Solution(object): 30 | def getSum(self, a, b): 31 | # 2^32 32 | MASK = 0x100000000 33 | # 整型最大值 34 | MAX_INT = 0x7FFFFFFF 35 | MIN_INT = MAX_INT + 1 36 | while b != 0: 37 | # 计算进位 38 | carry = (a & b) << 1 39 | # 取余范围限制在 [0, 2^32-1] 范围内 40 | a = (a ^ b) % MASK 41 | b = carry % MASK 42 | return a if a <= MAX_INT else ~((a % MIN_INT) ^ MAX_INT) 43 | ``` -------------------------------------------------------------------------------- /python/378. 有序矩阵中第K小的元素.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第k小的元素。 3 | 请注意,它是排序后的第k小元素,而不是第k个元素。 4 | ``` 5 | 示例: 6 | 7 | matrix = [ 8 | [ 1, 5, 9], 9 | [10, 11, 13], 10 | [12, 13, 15] 11 | ], 12 | k = 8, 13 | 14 | 返回 13。 15 | ``` 16 | 17 | ### 解析1: 18 | 二维数组展开,然后排序,返回第k个,暴力法,但是速度好像很快啊。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(mnlog(mn))$|212 ms|83.49%| 23 | |空间|$O(mn)$|19.7 MB|5.22%| 24 | 25 | ```python 26 | class Solution: 27 | def kthSmallest(self, matrix: List[List[int]], k: int) -> int: 28 | nums = [x for x in line for line in matrix] 29 | return sorted(nums)[k-1] 30 | ``` 31 | 32 | ### 解析2: 33 | 二维数组展开,展开后用堆寻找第k小的元素。明显应该更快,但是时间变成了,神奇的leetcode测评时间。 34 | 35 | | |复杂度|大小|百分比| 36 | |--|--|--|--| 37 | |时间|$O(mnlog(k))$|360 ms|40.12%| 38 | |空间|$O(mn)$|19.7 MB|5.22%| 39 | 40 | ```python 41 | import heapq 42 | class Solution: 43 | def kthSmallest(self, matrix: List[List[int]], k: int) -> int: 44 | nums = [x for line in matrix for x in line] 45 | return heapq.nsmallest(k, nums)[-1] 46 | ``` 47 | 48 | ### 解析3: 49 | -------------------------------------------------------------------------------- /python/387. 字符串中的第一个唯一字符.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 3 | ``` 4 | 案例: 5 | 6 | s = "leetcode" 7 | 返回 0. 8 | 9 | s = "loveleetcode", 10 | 返回 2. 11 | ``` 12 | 13 | ### 解析1: 14 | 哈希表统计每个词的出现次数,然后遍历即可。 15 | 16 | | |复杂度|大小|百分比| 17 | |--|--|--|--| 18 | |时间|$O(logn)$|172 ms|51.95%| 19 | |空间|$O(n)$|14 MB|5.34%| 20 | 21 | ```python 22 | from collections import Counter 23 | class Solution: 24 | def firstUniqChar(self, s: str) -> int: 25 | cnt = Counter(s) 26 | for i in range(len(s)): 27 | if cnt[s[i]] == 1: 28 | return i 29 | return -1 30 | ``` -------------------------------------------------------------------------------- /python/39..组合总和.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 3 | 4 | candidates 中的数字可以无限制重复被选取。 5 | 6 | 说明: 7 | 8 | 所有数字(包括 target)都是正整数。 9 | 解集不能包含重复的组合。 10 |   11 | ``` 12 | 示例 1: 13 | 14 | 输入: candidates = [2,3,6,7], target = 7, 15 | 所求解集为: 16 | [ 17 | [7], 18 | [2,2,3] 19 | ] 20 | ``` 21 | 22 | ### 解析1: 23 | 学好DFS保全家,多刷题总结模板,刷习惯就好了。 24 | 25 | | |复杂度|大小|百分比| 26 | |--|--|--|--| 27 | |时间|$O()$|60ms|67.20%| 28 | |空间|$O(1)$|11.9 MB|12.97%| 29 | 30 | 31 | 步骤: 32 | 1. 对candidates进行排序,避免重复; 33 | 2. 回溯算法: 34 | 1. 如果满足条件,输出结果; 35 | 2. 遍历候选数组,相当于深度遍历一棵树,需要进行剪枝,如果不满足要求,提前剪除; 36 | 3. 递归实现; 37 | 38 | 39 | ```python 40 | class Solution(object): 41 | def combinationSum(self, candidates, target): 42 | res = [] 43 | # 对候选数组进行排序,避免出现重复元素 44 | candidates = sorted(candidates) 45 | 46 | def backtrack(target, temp): 47 | if target == 0: 48 | res.append(temp) 49 | 50 | for num in candidates: 51 | if num > target:break 52 | if temp and num < temp[-1]:continue 53 | else: 54 | backtrack(target-num, temp+[num]) 55 | backtrack(target, []) 56 | return res 57 | ``` 58 | 59 | ### 回溯总结: 60 | 1. https://leetcode-cn.com/circle/article/GV6eQ2/ 61 | 2. https://www.cnblogs.com/wuyuegb2312/p/3273337.html 62 | 3. Leetcode 17.电话号码的字母组合 63 | 4. Leetcode 39.组合总和 64 | 5. Leetcode 40.组合总和II 65 | 6. Leetcode 46.全排列 66 | 7. Leetcode 47.全排列II 67 | 8. Leetcode 77.组合 68 | 9. Leetcode 78.子集 69 | 10. Leetcode 216.组合总和III 70 | 71 | 72 | 回溯模板: 73 | ```python 74 | 75 | ``` -------------------------------------------------------------------------------- /python/392. 判断子序列.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 3 | 4 | 你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。 5 | 6 | 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 7 | ``` 8 | 示例 1: 9 | s = "abc", t = "ahbgdc" 10 | 11 | 返回 true. 12 | ``` 13 | 14 | ### 解析1: 15 | 遍历字符串,判断目标字符串的第一个字符是否匹配,如果相互匹配,则去掉这个字符,一旦出现目标字符为空,同样返回True。 16 | 17 | 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(n)$|120 ms|57.87%| 21 | |空间|$O(n)$|18.9 MB|21.93%| 22 | 23 | 24 | ```python 25 | class Solution(object): 26 | def isSubsequence(self, s, t): 27 | s = list(s) 28 | for char in t: 29 | if not s: 30 | return True 31 | if char == s[0]: 32 | s.pop(0) 33 | 34 | return not s 35 | ``` -------------------------------------------------------------------------------- /python/394. 字符串解码.md: -------------------------------------------------------------------------------- 1 | ### 题目 2 | 给定一个经过编码的字符串,返回它解码后的字符串。 3 | 4 | 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 5 | 6 | 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 7 | 8 | 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。 9 | ``` 10 | 示例: 11 | 12 | s = "3[a]2[bc]", 返回 "aaabcbc". 13 | s = "3[a2[c]]", 返回 "accaccacc". 14 | s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef". 15 | ``` 16 | 17 | ### 解析1: 18 | 主要是栈的利用,将括号外的字符串不断保存,遇到新的然后组合起来。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|40 ms|96.28%| 23 | |空间|$O(n)$|13.8 MB|5.26%| 24 | 25 | 步骤: 26 | 1. 建立一个栈保存依次出现的字符和出现的次数; 27 | 2. 遍历字符串: 28 | 1. 如果字符是数字,保存到num:对应括号内要重复的次数; 29 | 2. 如果字符是字母,保存到this_str:是括号外的字符串; 30 | 3. 遇到'[',将this_str和num入栈,this_str对应这一个括号外的字符串,num对应括号内的字符串的重复次数 31 | 4. 同时更新this_str为空,num为0,因为this_str始终保存最新括号内的字符串,num保存括号内字符串的次数; 32 | 5. 遇到']',出栈last_str和this_num,last_str为括号外的字符串,this_num为括号和内字符串出现的次数; 33 | 6. 最后将括号内的字符串和括号外的字符串相加:this_str*this_num + last_str 34 | 35 | 36 | ```python 37 | class Solution: 38 | def decodeString(self, s: str) -> str: 39 | stack, this_str,num = [], '', 0 40 | for i in s: 41 | # 会出现‘123’这种字符所以需要num*10 + int(I) 42 | if i.isdigit():num = num * 10 + int(i) 43 | elif i.isalpha():this_str += i 44 | elif i == '[': 45 | stack.append((this_str,num)) 46 | this_str, num = '', 0 47 | else: # i == ']' 48 | last_str, this_num = stack.pop() 49 | this_str = last_str + this_num * this_str 50 | return this_str 51 | ``` -------------------------------------------------------------------------------- /python/395. 至少有K个重复字符的最长子串.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 找到给定字符串(由小写字符组成)中的最长子串 T , 要求 T 中的每一字符出现次数都不少于 k 。输出 T 的长度。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: 7 | s = "aaabb", k = 3 8 | 9 | 输出: 10 | 3 11 | 12 | 最长子串为 "aaa" ,其中 'a' 重复了 3 次。 13 | 示例 2: 14 | 15 | 输入: 16 | s = "ababbc", k = 2 17 | 18 | 输出: 19 | 5 20 | 21 | 最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。 22 | ``` 23 | 24 | ### 解析1: 25 | 分治递归,需要弄懂思想,将原字符串进行划分,直到不能划分为止。 26 | 27 | | |复杂度|大小|百分比| 28 | |--|--|--|--| 29 | |时间|$O(n)$|24 ms|87.72%| 30 | |空间|$O(n)$|12.1 MB|26.14% 31 | 32 | 步骤: 33 | 1. 如果字符串总长度小于k,返回0; 34 | 2. 出现次数最少的字符; 35 | 3. 如果出现次数最少的字符,次数大于k,返回当前字符串的长度; 36 | 4. 如果出现次数最少的字符,次数小于k,以这个字符为分隔符进行分割; 37 | 38 | 39 | ```python 40 | class Solution(object): 41 | def longestSubstring(self, s, k): 42 | if len(s) < k:return 0 43 | c = min(set(s), key=s.count) 44 | 45 | if s.count(c) >= k: 46 | return len(s) 47 | else: 48 | return max(self.longestSubstring(t,k) for t in s.split(c)) 49 | ``` -------------------------------------------------------------------------------- /python/40. 组合总和 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 3 | 4 | candidates 中的每个数字在每个组合中只能使用一次。 5 | 6 | 说明: 7 | 8 | 所有数字(包括目标数)都是正整数。 9 | 解集不能包含重复的组合。  10 | 11 | ``` 12 | 示例 1: 13 | 14 | 输入: candidates = [10,1,2,7,6,1,5], target = 8, 15 | 所求解集为: 16 | [ 17 | [1, 7], 18 | [1, 2, 5], 19 | [2, 6], 20 | [1, 1, 6] 21 | ] 22 | ``` 23 | 24 | ### 解析1: 25 | 和39类似,需要对candidates进行选取。每次选取当前元素后面的进行回溯。但是判断是否相同剪枝的地方有些不同。如果候选数组中两个数相同的,则进行剪枝,避免重复。 26 | 27 | | |复杂度|大小|百分比| 28 | |--|--|--|--| 29 | |时间|$O()$|60ms|60.89%| 30 | |空间|$O(1)$|11.9 MB|12.14%| 31 | 32 | ```python 33 | class Solution(object): 34 | def combinationSum2(self, candidates, target): 35 | res = [] 36 | # 对候选数组进行排序,避免出现重复元素 37 | candidates = sorted(candidates) 38 | n = len(candidates) 39 | 40 | def backtrack(i, target, temp): 41 | if target == 0: 42 | res.append(temp) 43 | 44 | for j in range(i,n): 45 | num = candidates[j] 46 | # 剪枝,如果大于target,break 47 | if num > target:break 48 | if j>i and candidates[j] == candidates[j-1]:continue 49 | else:backtrack(j+1, target-num, temp+[num]) 50 | 51 | backtrack(0, target, []) 52 | return res 53 | ``` -------------------------------------------------------------------------------- /python/401. 二进制手表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 二进制手表顶部有 4 个 LED 代表小时(0-11),底部的 6 个 LED 代表分钟(0-59)。 3 | 4 | 每个 LED 代表一个 0 或 1,最低位在右侧。 5 | 给定一个非负整数 n 代表当前 LED 亮着的数量,返回所有可能的时间。 6 | ``` 7 | 案例: 8 | 9 | 输入: n = 1 10 | 返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"] 11 | ``` 12 | 13 | ### 解析1: 14 | 很好玩的一个题,直接的想法是用回溯来做,但是如果知道所有的情况,而且复杂度又不是很高的话,可以考虑遍历所有的情况,然后判断是否满足条件。 15 | 16 | * **复杂度:** 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(1)$|52 ms|81.39%| 20 | |空间|$O(1)$|12.6 MB|23.34%| 21 | 因为计算时间是固定的,所以时间复杂度是$O(1)$。 22 | 23 | * **算法流程:** 24 | * 遍历所有时刻,小时0-11,分钟0-59; 25 | * 然后求一共有多少个1,是否满足题目要求,满足的话就返回到res里面; 26 | * 分钟小于的10的话需要注意; 27 | 28 | 29 | ```python 30 | class Solution: 31 | def readBinaryWatch(self, num: int) -> List[str]: 32 | res = [] 33 | def count(num): 34 | return bin(num).count('1') 35 | 36 | def count(num): 37 | # 这种求法更慢 38 | res = 0 39 | while num: 40 | res += num&1 41 | num >>= 1 42 | return res 43 | for i in range(12): 44 | for j in range(60): 45 | if count(i)+count(j) == num: 46 | if j<10: 47 | res.append(str(i)+':0'+str(j)) 48 | else: 49 | res.append(str(i)+':'+str(j)) 50 | return res 51 | ``` -------------------------------------------------------------------------------- /python/406. 根据身高重建队列.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。 3 | 4 | 注意: 5 | 总人数少于1100人。 6 | ``` 7 | 示例 8 | 9 | 输入: 10 | [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] 11 | 12 | 输出: 13 | [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] 14 | ``` 15 | 16 | ### 解析1: 17 | 对身高队列进行排序。身高按降序排序,人数按升序排序。最高的人的顺序,不依赖其他人的位置,所以他的位置为空数组插入p[1]的位置。后面的人,所处的位置,需依赖比他高的所有的人。所以从高到底开始插入。频次频次从大到小排序。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(nlogn)$|96 ms|91.38%| 22 | |空间|$O(n)$|12 MB|22.22%| 23 | 24 | 时间复杂度依赖于排序的复杂度。 25 | 26 | ```python 27 | class Solution(object): 28 | def reconstructQueue(self, people): 29 | # 对身高升序排序,对人数降序排序 30 | people = sorted(people, key=lambda x:[-x[0],x[1]]) 31 | res = [] 32 | for p in people: 33 | res.insert(p[1],p) 34 | return res 35 | ``` -------------------------------------------------------------------------------- /python/410. 分割数组的最大值.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。 3 | 4 | 注意: 5 | 数组长度 n 满足以下条件: 6 | 7 | 1 ≤ n ≤ 1000 8 | 1 ≤ m ≤ min(50, n) 9 | ``` 10 | 示例: 11 | 12 | 输入: 13 | nums = [7,2,5,10,8] 14 | m = 2 15 | 16 | 输出: 17 | 18 18 | ``` 19 | 20 | ### 解析1: 21 | 思路通,但是还是有些麻烦。 22 | 23 | 24 | ```python 25 | class Solution: 26 | def splitArray(self, nums: List[int], m: int) -> int: 27 | def countGroups(mid): 28 | temp = 0 29 | count = 1 30 | for num in nums: 31 | temp += num 32 | if temp > mid: 33 | count += 1 34 | temp = num # 准备下一组 35 | return count 36 | 37 | left, right = max(nums), sum(nums) 38 | 39 | while left < right: 40 | mid = left + (right - left) // 2 41 | num_group = countGroups(mid) 42 | 43 | if num_group > m: # 划分多了,mid太小了 44 | left = mid + 1 45 | else: 46 | right = mid 47 | print(left, mid, right) 48 | return left # left恰好是满足条件的最少分割,自然就最大 49 | ``` -------------------------------------------------------------------------------- /python/412. Fizz Buzz.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 写一个程序,输出从 1 到 n 数字的字符串表示。 3 | 4 | 1. 如果 n 是3的倍数,输出“Fizz”; 5 | 6 | 2. 如果 n 是5的倍数,输出“Buzz”; 7 | 8 | 3.如果 n 同时是3和5的倍数,输出 “FizzBuzz”。 9 | 10 | 11 | ### 解析1: 12 | 无他,直接遍历数字,然后拼接。 13 | 14 | | |复杂度|大小|百分比| 15 | |--|--|--|--| 16 | |时间|$O(n)$|76 ms|37.27%| 17 | |空间|$O(n)$|14.7 MB|5.25%| 18 | 19 | ```python 20 | class Solution: 21 | def fizzBuzz(self, n: int) -> List[str]: 22 | res = [] 23 | for i in range(1,n+1): 24 | if i % 3==0 and i%5 == 0: 25 | res.append('FizzBuzz') 26 | elif i % 3 == 0: 27 | res.append('Fizz') 28 | elif i % 5 == 0: 29 | res.append('Buzz') 30 | else: 31 | res.append(str(i)) 32 | return res 33 | ``` -------------------------------------------------------------------------------- /python/414. 第三大的数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非空数组,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。要求算法时间复杂度必须是O(n)。 3 | 4 | >示例 1: 5 | 输入: [3, 2, 1] 6 | 输出: 1 7 | 解释: 第三大的数是 1. 8 | 9 | ### 解析1: 10 | 排序然后返回,注意边界条件,nums数量小于3,重复的元素只能算一次所以需要取集合。 11 | 12 | 13 | 时间:O(nlogn) 14 | 空间:O(1) 15 | 16 | ```python 17 | class Solution(object): 18 | def thirdMax(self, nums): 19 | nums = sorted(list(set(nums))) 20 | if len(nums) < 3: 21 | return max(nums) 22 | return nums[-3] 23 | ``` 24 | 25 | ### 解析2: 26 | 依然是使用最小堆来实现。理论上这种方法应该更快,时间惊人的慢。 27 | 28 | 时间:O(nlogk) 29 | 空间:O(1) 30 | 31 | ```python 32 | class Solution(object): 33 | def thirdMax(self, nums): 34 | nums = list(set(nums)) 35 | if len(nums) < 3: 36 | return max(nums) 37 | return heapq.nlargest(3, nums)[-1] 38 | ``` 39 | 40 | 三个数,数量较小,可以直接比较。 41 | -------------------------------------------------------------------------------- /python/43. 字符串相乘.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: num1 = "2", num2 = "3" 7 | 输出: "6" 8 | ``` 9 | 10 | ### 解析1: 11 | 投机取巧系列。 12 | 13 | | |复杂度|大小|百分比| 14 | |--|--|--|--| 15 | |时间|$O(mn)$|24 ms|89.49%| 16 | |空间|$O(n)$|11.7 MB|37.63%| 17 | 18 | 19 | ```python 20 | class Solution(object): 21 | def multiply(self, num1, num2): 22 | return str(int(num1)*int(num2)) 23 | ``` 24 | 25 | 字符串模拟竖式相乘。 -------------------------------------------------------------------------------- /python/437. 路径总和 III.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,它的每个结点都存放着一个整数值。 3 | 4 | 找出路径和等于给定数值的路径总数。 5 | 6 | 路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。 7 | 8 | 二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。 9 | 10 | ``` 11 | 示例: 12 | 13 | root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 14 | 15 | 10 16 | / \ 17 | 5 -3 18 | / \ \ 19 | 3 2 11 20 | / \ \ 21 | 3 -2 1 22 | 23 | 返回 3。和等于 8 的路径有: 24 | 25 | 1. 5 -> 3 26 | 2. 5 -> 2 -> 1 27 | 3. -3 -> 11 28 | ``` 29 | 30 | ### 解析1: 31 | 迭代,将到达每一个结点的路径段,均保存在一个list中,然后拼成一个元组保存在stack中。 32 | 33 | | |复杂度|大小|百分比| 34 | |--|--|--|--| 35 | |时间|$O(n)$|224 ms|87.55%| 36 | |空间|$O(n)$|12.4 MB|50.83%| 37 | 38 | 步骤: 39 | 1. 建立一个栈,保存元组,元组内为结点和到达该结点的所有路径段; 40 | 2. 遍历栈,此处是一样的,但是加一个判断,判断在包含当前结点的所有路径段中total的个数; 41 | 3. 添加新的结点,主要是路径段的添加,已知的每一个路径段,加上当前结点值,以及当前结点值:[x+node.right.val for x in totals]+[node.right.val]; 42 | 4. 左右结点同样类似; 43 | 44 | ```python 45 | class Solution(object): 46 | def pathSum(self, root, total): 47 | if not root: 48 | return 0 49 | 50 | stack = [(root, [root.val])] 51 | num = 0 52 | 53 | while stack: 54 | node, totals = stack.pop() 55 | 56 | num += totals.count(total) 57 | if node.right: 58 | stack.append((node.right, [x+node.right.val for x in totals]+[node.right.val])) 59 | 60 | if node.left: 61 | stack.append((node.left, [x+node.left.val for x in totals]+[node.left.val])) 62 | 63 | return num 64 | ``` -------------------------------------------------------------------------------- /python/448. 找到所有数组中消失的数字.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。 3 | 4 | 找到所有在 [1, n] 范围之间没有出现在数组中的数字。 5 | 6 | 您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。 7 | ``` 8 | 示例: 9 | 10 | 输入: 11 | [4,3,2,7,8,2,3,1] 12 | 13 | 输出: 14 | [5,6] 15 | ``` 16 | 17 | ### 解析1: 18 | 遍历数组,判断是否出现。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|80 ms|67.44%| 23 | |空间|$O(n)$|13.9 MB|8.64%| 24 | 25 | ```python 26 | class Solution(object): 27 | def findDisappearedNumbers(self, nums): 28 | n,res = len(nums),[] 29 | nums = set(nums) 30 | for i in range(1,n+1): 31 | if i not in nums: 32 | res.append(i) 33 | return res 34 | ``` 35 | 36 | ### 解析2: 37 | 骚操作,数组中存在的数,将其作为索引,索引对应原数组的数求反。最后提取大于0的数组元素的索引。 38 | 39 | | |复杂度|大小|百分比| 40 | |--|--|--|--| 41 | |时间|$O(n)$|760 ms|10.75%| 42 | |空间|$O(1)$|19 MB|50.62%| 43 | 44 | 步骤: 45 | 1. 遍历数组,将abs(num)-1作为索引,对对应的数值求反; 46 | 2. 数组中存在的数字,在nums中对应的索引便变成负; 47 | 3. 求数组中依然大于0的元素,其索引+1便是所求结果; 48 | 49 | 50 | 51 | ```python 52 | class Solution(object): 53 | def findDisappearedNumbers(self, nums): 54 | for num in nums: 55 | index = abs(num)-1 56 | nums[index] = -abs(nums[index]) 57 | 58 | return [i+1 for i in range(n) if nums[i] > 0] 59 | ``` -------------------------------------------------------------------------------- /python/45. 跳跃游戏 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非负整数数组,你最初位于数组的第一个位置。 3 | 4 | 数组中的每个元素代表你在该位置可以跳跃的最大长度。 5 | 6 | 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 7 | ``` 8 | 示例: 9 | 10 | 输入: [2,3,1,1,4] 11 | 输出: 2 12 | 解释: 跳到最后一个位置的最小跳跃数是 2。 13 |   从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。 14 | ``` 15 | 16 | ### 解析1: 17 | 动态规划。 18 | 19 | 状态含义:dp[i]到达第i个点所需的最少步数; 20 | 状态方程:dp[i] = max(dp[i], dp[j]+1) for j in [0,i-1] if nums[j] >= i-j 21 | 22 | | |复杂度|大小|百分比| 23 | |--|--|--|--| 24 | |时间|$O(n^2)$|超时| | 25 | |空间|$O(n)$| | | 26 | 27 | ```python 28 | class Solution(object): 29 | def jump(self, nums): 30 | n = len(nums) 31 | dp = [float('inf')] * n 32 | dp[0] = 0 33 | for i in range(1,n): 34 | for j in range(i): 35 | if nums[j] >= (i-j): 36 | dp[i] = min(dp[i], dp[j]+1) 37 | return dp[n-1] 38 | ``` 39 | 40 | ### 解析2: 41 | 贪心法,每次跳跃到最远的边界。 42 | 43 | | |复杂度|大小|百分比| 44 | |--|--|--|--| 45 | |时间|$O(n)$|172 ms|13.19%| 46 | |空间|$O(n)$|13.5 MB|14.77%| 47 | 48 | ```python 49 | class Solution: 50 | def jump(self, nums): 51 | n = len(nums) 52 | if n < 2: 53 | return 0 54 | 55 | # 跳跃计数,初始值为1 56 | jump_num = 1 57 | # 元素能够跳跃到的最大位置 58 | max_dist = nums[0] 59 | # 由max_dist得出跳跃的边界 60 | border = nums[0] 61 | for i in range(n - 1): 62 | # 遍历两个边界之间的元素,求出最大元素位置和最大跳跃数之和,产生新的边界 63 | max_dist = max(max_dist, i + nums[i]) 64 | # 元素每次到达跳跃的边界,则重新更新边界,跳跃计数增加1 65 | if i == border: 66 | border = max_dist 67 | jump_num += 1 68 | 69 | return jump_num 70 | ``` -------------------------------------------------------------------------------- /python/46. 全排列.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个没有重复数字的序列,返回其所有可能的全排列。 3 | 4 | >示例: 5 | 输入: [1,2,3] 6 | 输出: 7 | [ 8 | [1,2,3], 9 | [1,3,2], 10 | [2,1,3], 11 | [2,3,1], 12 | [3,1,2], 13 | [3,2,1] 14 | ] 15 | 16 | 17 | ### 解析1: 18 | 直接使用python自带的工具来实现。使用`itertools.permutations`产生数组的额全排列。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间||32 ms|76.07%| 23 | |空间|$O(N!)$|11.7 MB|38.23%| 24 | 25 | 26 | ```python 27 | import itertools 28 | class Solution(object): 29 | def permute(self, nums): 30 | return list(itertools.permutations(nums)) 31 | ``` 32 | 33 | ### 解析2: 34 | 手动实现上述算法。主要便是回溯法,对于回溯的理解依然不够,需要做更多的题。 35 | 36 | | |复杂度|大小|百分比| 37 | |--|--|--|--| 38 | |时间||68 ms|50.43%| 39 | |空间|$O(N!)$|14.1 MB|5.27%| 40 | 41 | 步骤: 42 | 1. 建立一个res保存所有满足条件的排列; 43 | 2. 回溯算法: 44 | 1. 结束条件:如果nums为空则结束; 45 | 2. 选择:nums内的每一个数,进行选择,将回溯算法嵌套在for循环里面,去掉被循环的元素; 46 | 47 | ```python 48 | class Solution(object): 49 | def permute(self, nums): 50 | res = [] 51 | def backtrack(nums, temp): 52 | if len(nums) == 0: 53 | res.append(temp) 54 | for i in range(len(nums)): 55 | backtrack(nums[:i]+nums[i+1:], temp+[nums[i]]) 56 | backtrack(nums, []) 57 | return res 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /python/461. 汉明距离.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。 3 | 4 | 给出两个整数 x 和 y,计算它们之间的汉明距离。 5 | 6 | 注意: 7 | 0 ≤ x, y < 231. 8 | 9 | ``` 10 | 示例: 11 | 12 | 输入: x = 1, y = 4 13 | 14 | 输出: 2 15 | 16 | 解释: 17 | 1 (0 0 0 1) 18 | 4 (0 1 0 0) 19 | ↑ ↑ 20 | 21 | 上面的箭头指出了对应二进制位不同的位置。 22 | ``` 23 | 24 | ### 解析1: 25 | 两个数做异或,然后统计二进制中1的个数。不同为1,相同为0。 26 | 27 | | |复杂度|大小|百分比| 28 | |--|--|--|--| 29 | |时间|$O(n)$|20 ms|82.50%| 30 | |空间|$O(n)$|11.8 MB|18.10%| 31 | 32 | 33 | ```python 34 | class Solution(object): 35 | def hammingDistance(self, x, y): 36 | return bin(x^y).count('1') 37 | ``` 38 | 39 | ### 解析2: 40 | 通过位运算来求1的个数。依次判断最右边是否是1,然后移位。 41 | 42 | | |复杂度|大小|百分比| 43 | |--|--|--|--| 44 | |时间|$O(n)$|28 ms|36.21%| 45 | |空间|$O(n)$|11.7 MB|25.46%| 46 | 47 | ```python 48 | class Solution: 49 | def hammingDistance(self,x, y): 50 | count = 0 51 | x = x ^ y 52 | while True: 53 | if x & 1 == 1: 54 | count += 1 55 | if x == 0: 56 | return count 57 | x >>= 1 58 | ``` 59 | 60 | ### 解析3: 61 | 转换成二进制,然后进行比较,比较不同位数的数目。 62 | 63 | * **复杂度:** 64 | | |复杂度|大小|百分比| 65 | |--|--|--|--| 66 | |时间|$O(n)$|24 ms|98%| 67 | |空间|$O(1)$|13.4 MB|23.34%| 68 | 69 | ```python 70 | class Solution: 71 | def hammingDistance(self, x: int, y: int) -> int: 72 | x = bin(x)[2:] 73 | y = bin(y)[2:] 74 | maxlen = max(len(x),len(y)) 75 | x = x.zfill(maxlen) 76 | y = y.zfill(maxlen) 77 | res = 0 78 | 79 | for i in range(maxlen): 80 | if x[i] != y[i]: 81 | res += 1 82 | return res 83 | ``` -------------------------------------------------------------------------------- /python/47.全排列II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个可包含重复数字的序列,返回所有不重复的全排列。 3 | 4 | 示例: 5 | 6 | 输入: [1,1,2] 7 | 输出: 8 | [ 9 | [1,1,2], 10 | [1,2,1], 11 | [2,1,1] 12 | ] 13 | 14 | ### 解析1: 15 | 依然是回溯法,只不过每次在添加新排列的时候,判断是否在res中出现过。 16 | 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间||2188 ms|5%| 20 | |空间|$O(N!)$|12.2 MB|11.93%| 21 | 22 | ```python 23 | class Solution(object): 24 | def permuteUnique(self, nums): 25 | res = [] 26 | def backtrack(nums, temp): 27 | if len(nums) == 0 and temp not in res: 28 | res.append(temp) 29 | for i in range(len(nums)): 30 | backtrack(nums[:i]+nums[i+1:], temp+[nums[i]]) 31 | backtrack(nums, []) 32 | return res 33 | ``` 34 | 35 | ### 解析2: 36 | 和上述方法相似,再递归的时候剪枝,而不是判断是否在res中出现过。 37 | 38 | | |复杂度|大小|百分比| 39 | |--|--|--|--| 40 | |时间||40 ms|98.37%| 41 | |空间|$O(N!)$|12.3 MB|8.42%| 42 | 43 | ```python 44 | class Solution(object): 45 | def permuteUnique(self, nums): 46 | res = [] 47 | nums = sorted(nums) 48 | 49 | def backtrack(nums, temp): 50 | if len(nums) == 0: 51 | res.append(temp) 52 | 53 | for i in range(len(nums)): 54 | if i > 0 and nums[i] == nums[i-1]: 55 | continue 56 | backtrack(nums[:i] + nums[i+1:], temp+[nums[i]]) 57 | 58 | backtrack(nums, []) 59 | return res 60 | ``` 61 | ### 解析3: 62 | 这种方式应该同样也可以生成全排列,但是顺序被打乱了。 63 | 64 | ```python 65 | class Solution(object): 66 | def permuteUnique(self, nums): 67 | res = list(itertools.permutations(nums)) 68 | res = [''.join(map(lambda x:str(x), x)) for x in res] 69 | res = list(set(res)) 70 | res = [[x for x in temp] for temp in res] 71 | return res 72 | ``` -------------------------------------------------------------------------------- /python/477. 汉明距离总和.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 两个整数的 汉明距离 指的是这两个数字的二进制数对应位不同的数量。 3 | 4 | 计算一个数组中,任意两个数之间汉明距离的总和。 5 | 6 | ``` 7 | 示例: 8 | 9 | 输入: 4, 14, 2 10 | 11 | 输出: 6 12 | 13 | 解释: 在二进制表示中,4表示为0100,14表示为1110,2表示为0010。(这样表示是为了体现后四位之间关系) 14 | 所以答案为: 15 | HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6. 16 | ``` 17 | 18 | ### 解析1: 19 | 利用461.汉明距离,计算数组内每两对数的汉明距离,然后求和。但是超时。 20 | 21 | n:数据量;m二进制位数; 22 | | |复杂度|大小|百分比| 23 | |--|--|--|--| 24 | |时间|$O(n^2)$||| 25 | |空间|$O(nm)$||| 26 | 27 | ```python 28 | class Solution(object): 29 | def totalHammingDistance(self, nums): 30 | def HD(num1, num2): 31 | return bin(num1^num2).count('1') 32 | res = 0 33 | n = len(nums) 34 | for i in range(n-1): 35 | for j in range(i+1, n): 36 | res += HD(nums[i],nums[j]) 37 | return res 38 | ``` 39 | 40 | ### 解析2: 41 | * **算法流程:** 42 | * 遍历32位,依次比较每一位; 43 | * 比较每一位,数组中所有数1的个数cnt1和0的个数cnt0,这一位不相同的种数便是cnt1*cnt0; 44 | * 将每一位不同的组合数相加 45 | 46 | 将横向比较,变为纵向比较。 47 | 48 | * **复杂度:** 49 | | |复杂度|大小|百分比| 50 | |--|--|--|--| 51 | |时间|$O(36n)$|952 ms|11.25%| 52 | |空间|$O(nm)$|12.4 MB|40.00%| 53 | 54 | ```python 55 | class Solution(object): 56 | def totalHammingDistance(self, nums): 57 | res = 0 58 | n = len(nums) 59 | for i in range(32): 60 | cnt1 = 0 61 | for i in range(n): 62 | if nums[i] & 1 != 0: 63 | cnt1 += 1 64 | nums[i] >>= 1 65 | res += cnt1*(n-cnt1) 66 | return res 67 | ``` 68 | -------------------------------------------------------------------------------- /python/48. 旋转图像.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 3 | 给定一个 n × n 的二维矩阵表示一个图像。 4 | 5 | 将图像顺时针旋转 90 度。 6 | 7 | 说明: 8 | 9 | 你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。 10 | 11 | ``` 12 | 示例 1: 13 | 14 | 给定 matrix = 15 | [ 16 | [1,2,3], 17 | [4,5,6], 18 | [7,8,9] 19 | ], 20 | 21 | 原地旋转输入矩阵,使其变为: 22 | [ 23 | [7,4,1], 24 | [8,5,2], 25 | [9,6,3] 26 | ] 27 | ``` 28 | 29 | ### 解析1: 30 | 找规律,我们发现,图像旋转,即图像矩阵经过了一次颠倒操作,然后转置。题目的问题是原地操作。 31 | 32 | | |复杂度|大小|百分比| 33 | |--|--|--|--| 34 | |时间|$O(n^2)$|52 ms|78.46%| 35 | |空间|$O(1)$|13.8 MB|5.06%| 36 | 37 | 步骤: 38 | 1. 对图像矩阵进行颠倒; 39 | 2. 图形矩阵进行转置; 40 | 41 | ```python 42 | class Solution: 43 | def rotate(self, matrix): 44 | matrix[:] = matrix[::-1] 45 | n = len(matrix[0]) 46 | # transpose matrix 47 | for i in range(n): 48 | for j in range(i, n): 49 | matrix[j][i], matrix[i][j] = matrix[i][j], matrix[j][i] 50 | ``` -------------------------------------------------------------------------------- /python/49. 字母异位词分组.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。 3 | ``` 4 | 示例: 5 | 6 | 输入: ["eat", "tea", "tan", "ate", "nat", "bat"], 7 | 输出: 8 | [ 9 | ["ate","eat","tea"], 10 | ["nat","tan"], 11 | ["bat"] 12 | ] 13 | ``` 14 | 15 | ### 解析1: 16 | 将排序字符串作为key,value为相同排序字符串相同的字符串组成的list。 17 | 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(nklogk)$|120 ms|99.03%| 21 | |空间|$O(nk)$|16.6 MB|26.73%| 22 | 23 | 步骤: 24 | 1. 建立一个字典,保存数据,对字符串排序后作为key,原字符串组成的list作为value。 25 | 2. 遍历字符串list: 26 | 1. 如果字符串在dict里面,拼接新字符串; 27 | 2. 如果不在,新建key和value将其保存; 28 | 29 | 30 | ```python 31 | class Solution: 32 | def groupAnagrams(self, strs: List[str]) -> List[List[str]]: 33 | res = dict() 34 | for str1 in strs: 35 | temp = ''.join(sorted(str1)) 36 | if temp in res: 37 | res[temp].append(str1) 38 | else: 39 | res[temp] = [str1] 40 | return res.values() 41 | ``` 42 | 43 | ### 解析2: 44 | 当数据量大后,这个方法会更好一点,但是需要空间稍大一些。Leetcode上时间更长一些,主要是用例少的原因。 45 | 46 | 建议一个list,大小为26,保存没一个数出现的次数,然后将其转变成tuple,如果出现次数相同,则是异位词。 47 | 48 | | |复杂度|大小|百分比| 49 | |--|--|--|--| 50 | |时间|$O(nk)$|172 ms|40.03%| 51 | |空间|$O(nk)$|19 MB|5.13%| 52 | 53 | ```python 54 | class Solution: 55 | def groupAnagrams(self, strs: List[str]) -> List[List[str]]: 56 | res = dict() 57 | for s in strs: 58 | count = [0] * 26 59 | for c in s: 60 | count[ord(c) - ord('a')] += 1 61 | count = tuple(count) 62 | if count in res: 63 | res[count].append(s) 64 | else: 65 | res[count] = [s] 66 | return res.values() 67 | ``` -------------------------------------------------------------------------------- /python/494. 目标和.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。 3 | 4 | 返回可以使最终数组和为目标数 S 的所有添加符号的方法数。 5 | 6 | ``` 7 | 示例 1: 8 | 9 | 输入: nums: [1, 1, 1, 1, 1], S: 3 10 | 输出: 5 11 | 解释: 12 | 13 | -1+1+1+1+1 = 3 14 | +1-1+1+1+1 = 3 15 | +1+1-1+1+1 = 3 16 | +1+1+1-1+1 = 3 17 | +1+1+1+1-1 = 3 18 | 19 | 一共有5种方法让最终目标和为3。 20 | ``` 21 | 22 | 23 | ### 解析1: 24 | 回溯法,遍历所有的可能。暴力回溯,直接超时。 25 | 状态含义:$f(i,target)$:[0:i]这个区间以target为结尾的方法数 26 | 状态方程:$f(i,target) = f(i-1,target-nums[i]) + f(i-1, target+nums[i])$ 27 | 28 | | |复杂度|大小|百分比| 29 | |--|--|--|--| 30 | |时间|$O(2^n)$|超时| | 31 | |空间|$O(1)$| | | 32 | 33 | ```python 34 | class Solution: 35 | def findTargetSumWays(self, nums: List[int], S: int) -> int: 36 | return self.backtrack(nums, 0, S) 37 | 38 | def backtrack(self, nums, index, target): 39 | if index == len(nums): 40 | if target == 0: 41 | return 1 42 | else: 43 | return 0 44 | 45 | return self.backtrack(nums, index+1, target-nums[index]) + \ 46 | self.backtrack(nums, index+1, target+nums[index]) 47 | ``` 48 | 49 | ### 解析1: 50 | 转换成0-1背包问题。 51 | 52 | | |复杂度|大小|百分比| 53 | |--|--|--|--| 54 | |时间|$O(n^2)$|100 ms|85.31%| 55 | |空间|$O(n)$|13.9 MB|7.35%| 56 | 57 | ```python 58 | class Solution: 59 | def findTargetSumWays(self, nums: List[int], S: int) -> int: 60 | if sum(nums) < S or (sum(nums) + S) % 2 == 1: return 0 61 | P = (sum(nums) + S) // 2 62 | dp = [1] + [0 for _ in range(P)] 63 | for num in nums: 64 | for j in range(P,num-1,-1):dp[j] += dp[j - num] 65 | return dp[P] 66 | ``` -------------------------------------------------------------------------------- /python/50. Pow(x, n).md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 实现 pow(x, n) ,即计算 x 的 n 次幂函数。 3 | 4 | 示例 1: 5 | 6 | 输入: 2.00000, 10 7 | 输出: 1024.00000 8 | 9 | ### 解析1: 10 | 调用函数实现基本操作。 11 | 12 | ```python 13 | class Solution: 14 | def myPow(self, x: float, n: int) -> float: 15 | return x**n 16 | ``` 17 | 18 | ### 解析2: 19 | 分治法,不断对其进行二分。当然肯定不如python自己实现的更快。主要注意边界条件,递归时,参数的迭代更新。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(logn)$|48 ms|71.81%| 24 | |空间|$O(1)$|13.8 MB|5.24%| 25 | 26 | ```python 27 | class Solution: 28 | def myPow(self, x: float, n: int) -> float: 29 | 30 | def helper(x,n): 31 | if n == 0:return 1 32 | if n%2 == 0:return helper(x*x,n//2) 33 | return helper(x*x, (n-1)//2)*x 34 | 35 | if n >= 0:return helper(x,n) 36 | return 1/helper(x,-n) 37 | ``` 38 | -------------------------------------------------------------------------------- /python/503. 下一个更大元素 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: [1,2,1] 8 | 输出: [2,-1,2] 9 | 解释: 第一个 1 的下一个更大的数是 2; 10 | 数字 2 找不到下一个更大的数; 11 | 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。 12 | ``` 13 | 14 | ### 解析1: 15 | 思路和496是一样的,只不过加入了一个循环数组。没什么太大不同,将nums重复一遍,如果循环后出现了肯定能在后面找到。最后返回前n个即可。 16 | 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(n)$|268ms|43.18%| 20 | |空间|$O(n)$|14.1MB|11.88%| 21 | 22 | 步骤: 23 | 1. 将原数组复制一遍 24 | ```python 25 | class Solution(object): 26 | def nextGreaterElements(self, nums): 27 | nums2 = nums + nums 28 | n = len(nums) 29 | res,stack = [-1]*2*n, [] 30 | for i,v in enumerate(nums2): 31 | while stack and stack[-1][1] < v: 32 | res[stack[-1][0]] = v 33 | stack.pop() 34 | stack.append([i,nums2[i]]) 35 | return res[:n] 36 | ``` -------------------------------------------------------------------------------- /python/509. 斐波那契数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: 3 | 4 | F(0) = 0,   F(1) = 1 5 | F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 6 | 给定 N,计算 F(N)。 7 | 8 | ``` 9 | 示例 1: 10 | 11 | 输入:2 12 | 输出:1 13 | 解释:F(2) = F(1) + F(0) = 1 + 0 = 1. 14 | ``` 15 | 16 | 17 | ### 解析1: 18 | 本质是DP,对DP进行了拆解。 19 | 20 | * **复杂度:** 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|36 ms|63.40%| 24 | |空间|$O(1)$|13.1 MB|49.07%| 25 | 26 | ```python 27 | class Solution: 28 | def fib(self, n: int) -> int: 29 | if n == 0:return 0 30 | f1,f2 = 0,1 31 | for i in range(n-1): 32 | f2,f1 = f1+f2,f2 33 | return f2 % 1000000007 34 | ``` -------------------------------------------------------------------------------- /python/523. 连续的子数组和.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个包含非负数的数组和一个目标整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: [23,2,4,6,7], k = 6 8 | 输出: True 9 | 解释: [2,4] 是一个大小为 2 的子数组,并且和为 6。 10 | 11 | 示例 2: 12 | 13 | 输入: [23,2,6,4,7], k = 6 14 | 输出: True 15 | 解释: [23,2,6,4,7]是大小为 5 的子数组,并且和为 42。 16 | ``` 17 | 18 | ### 解析1: 19 | 遍历数组,求子数组的和,然后判断子数组和是否是k的倍数,此处需要注意0的存在,也就是k为0的情况。代码只需判断何时返回True即可。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n^2)$|908 ms|16.48%| 24 | |空间|$O(1)$|12.3 MB|5.55%| 25 | 26 | 步骤: 27 | 1. 判断k为0的时候,temp_sum是否为0,并且j-i>0(子数组大小至少2); 28 | 2. k不为0的时候,即判断是否正整除k; 29 | 30 | ```python 31 | class Solution(object): 32 | def checkSubarraySum(self, nums, k): 33 | n =len(nums) 34 | for i in range(n): 35 | temp = 0 36 | for j in range(i+1, n): 37 | temp += nums[j] 38 | if k == 0 and temp ==0 or and j-i>0:return True 39 | if k != 0 and temp % k == 0 and j-i>0:return True 40 | return False 41 | ``` 42 | 简单优化下代码,没啥区别。 43 | 44 | ```python 45 | class Solution(object): 46 | def checkSubarraySum(self, nums, k): 47 | n = len(nums) 48 | 49 | for i in range(n-1): 50 | temp = nums[i] 51 | for j in range(i+1, n): 52 | temp += nums[j] 53 | if k == 0 and temp == 0 or k != 0 and temp % k ==0: 54 | return True 55 | return False 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /python/53. 最大子序和.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 3 | 4 | 示例: 5 | 输入: [-2,1,-3,4,-1,2,1,-5,4], 6 | 输出: 6 7 | 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 8 | 9 | ### 解析1: 10 | 暴力法不说了直接动态规划。 11 | 动态规划,f(n)以n为结尾的连续子序列和的最大值,则$f(n) = max(f(n-1)+nums[n], nums[n])$。最后求最大子序列和应该是以n为结尾的子序列和不断求最大。 12 | 13 | | |复杂度|大小|百分比| 14 | |--|--|--|--| 15 | |时间|$O(n)$|64 ms|80.14%| 16 | |空间|$O(1)$|12.3 MB|22.73%| 17 | 18 | 19 | ```python 20 | class Solution(object): 21 | def maxSubArray(self, nums): 22 | if nums == []:return 0 23 | n = len(nums) 24 | res = nums[0] 25 | temp = nums[0] 26 | for i in range(1, n): 27 | temp = max(nums[i], temp+nums[i]) 28 | res = max(res, temp) 29 | return res 30 | ``` -------------------------------------------------------------------------------- /python/538. 把二叉搜索树转换为累加树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。 3 | 4 | ``` 5 | 例如: 6 | 7 | 输入: 二叉搜索树: 8 | 5 9 | / \ 10 | 2 13 11 | 12 | 输出: 转换为累加树: 13 | 18 14 | / \ 15 | 20 13 16 | ``` 17 | 18 | ### 解析1: 19 | 递归遍历。DFS,先遍历右子树,保存结点值,对根结点值进行累加,然后遍历左子树。相当于中序遍历的倒序,依次将前一个结点值的加在当前结点。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|60 ms|98.42%| 24 | |空间|$O(n)$|16.3 MB|10.94%| 25 | 26 | ```python 27 | class Solution(object): 28 | def convertBST(self, root): 29 | self.val = 0 30 | 31 | def dfs(root): 32 | if not root:return 33 | dfs(root.right) 34 | self.val += root.val 35 | root.val = self.val 36 | dfs(root.left) 37 | 38 | dfs(root) 39 | return root 40 | ``` 41 | 42 | 二叉树的搜索查找和插入:https://blog.csdn.net/yht201293018/article/details/81229372 -------------------------------------------------------------------------------- /python/543. 二叉树的直径.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。 3 | ``` 4 | 示例 : 5 | 给定二叉树 6 | 7 | 1 8 | / \ 9 | 2 3 10 | / \ 11 | 4 5 12 | 返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。 13 | 14 | 注意:两结点之间的路径长度是以它们之间边的数目表示。 15 | ``` 16 | 17 | ### 解析1: 18 | 其实是求树最大深度的变种,和求树的深度相类似,在求深度的同时,更新直径。递归遍历每一个结点,求当前节点的左右子树的深度,同时更新以当前结点为路径起点的路径直径。注意最后的返回值,即路径的定义。 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|56 ms|95.77%| 23 | |空间|$O(n)$|16.3 MB|5.10%| 24 | 25 | 步骤: 26 | 1. 遍历树的每个结点,求以其为根节点的树深度; 27 | 2. 求以当前结点为公共祖先的两个叶节点的路径:左子树深度+右子树深度+1 28 | ```python 29 | class Solution: 30 | def diameterOfBinaryTree(self, root: TreeNode) -> int: 31 | self.ans = 1 32 | def helper(root): 33 | if not root:return 0 34 | L = helper(root.left) 35 | R = helper(root.right) 36 | self.ans = max(self.ans, L+R+1) 37 | return max(L,R) + 1 38 | helper(root) 39 | return self.ans-1 40 | ``` -------------------------------------------------------------------------------- /python/55. 跳跃游戏.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个非负整数数组,你最初位于数组的第一个位置。 3 | 4 | 数组中的每个元素代表你在该位置可以跳跃的最大长度。 5 | 6 | 判断你是否能够到达最后一个位置。 7 | 8 | 示例 1: 9 | 10 | 输入: [2,3,1,1,4] 11 | 输出: true 12 | 解释: 从位置 0 到 1 跳 1 步, 然后跳 3 步到达最后一个位置。 13 | 14 | ### 解析1: 15 | 动态规划,但是超时了,很迷。复杂度是$O(n^2)$估计需要$O(n)$。 16 | 17 | 状态含义:dp[i]能否到达第i个位置,如果能到达为1,如果不能到达为0; 18 | 状态方程:dp[i] = 1 if dp[i-j]*nums[i-j] >= j else 0 for j in [1,i]; 19 | 就是对i前面的数进行遍历,判断能否能否从这个数跳到i,j为距离i这个数的长度。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n^2)$|超时| | 24 | |空间|$O(n)$| | | 25 | 26 | 27 | ```python 28 | class Solution(object): 29 | def canJump(self, nums): 30 | n = len(nums) 31 | dp = [0] * n 32 | dp[0] = 1 33 | for i in range(1,n): 34 | for j in range(1,i+1): 35 | if dp[i-j] * nums[i-j] >= j: 36 | dp[i] = 1 37 | return dp[n-1] == 1 38 | ``` 39 | 40 | ### 解析2: 41 | 贪心法,求每一步能到的最大距离,如果start大于end需要停止,代表无法直接到达start这个位置。start代表跳跃的起始点,end代表跳跃的终点。因为需要判断是否能跳到最后一个点,所以start需要小于n-1。 42 | 43 | | |复杂度|大小|百分比| 44 | |--|--|--|--| 45 | |时间|$O(n)$|72 ms|67.69%| 46 | |空间|$O(n)$|13.3 MB|54.29%| 47 | 48 | ```python 49 | class Solution(object): 50 | def canJump(self, nums): 51 | n = len(nums) 52 | start,end = 0,0 53 | while start <= end and start < n-1: 54 | end = max(end, nums[start]+start) 55 | start += 1 56 | return end >= n-1 57 | ``` -------------------------------------------------------------------------------- /python/557. 反转字符串中的单词 III.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: "Let's take LeetCode contest" 7 | 输出: "s'teL ekat edoCteeL tsetnoc"  8 | 注意:在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。 9 | ``` 10 | 11 | ### 解析1: 12 | 13 | | |复杂度|大小|百分比| 14 | |--|--|--|--| 15 | |时间|$O(n)$|32 ms|100%| 16 | |空间|$O(logn)$|14.3 MB|5.04%| 17 | 18 | ```python 19 | class Solution: 20 | def reverseWords(self, s: str) -> str: 21 | return ' '.join([x[::-1] for x in s.split()]) 22 | ``` -------------------------------------------------------------------------------- /python/559. N叉树的最大深度.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个 N 叉树,找到其最大深度。 3 | 4 | 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 5 | 6 | 例如,给定一个 3叉树 : 7 | 8 | 我们应返回其最大深度,3。 9 | 10 | 说明: 11 | 12 | 树的深度不会超过 1000。 13 | 树的节点总不会超过 5000。 14 | 15 | ### 解析1: 16 | DFS递归遍历。 17 | 18 | * **算法流程:** 19 | 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|52 ms|63%| 24 | |空间|$O(logN)$|15.5 MB|26.14%| 25 | 26 | 27 | ```python 28 | class Solution: 29 | def maxDepth(self, root: 'Node') -> int: 30 | if not root:return 0 31 | if not root.children:return 1 32 | # max的list不能为空 33 | return max([self.maxDepth(node) for node in root.children]) + 1 34 | ``` -------------------------------------------------------------------------------- /python/59. 螺旋矩阵 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。 3 | ``` 4 | 示例: 5 | 6 | 输入: 3 7 | 输出: 8 | [ 9 | [ 1, 2, 3 ], 10 | [ 8, 9, 4 ], 11 | [ 7, 6, 5 ] 12 | ] 13 | ``` 14 | 15 | ### 解析1: 16 | 和54思路没啥差别,只不过反向操作,将[1,n**2]遍历,通过一个方向指针来判断方向。增加一个判断条件,是否已经遍历过,对数组进行了赋值。 17 | 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(n^2)$|24 ms|72.99%| 21 | |空间|$O(n)$|11.7 MB|31.63%| 22 | 23 | ```python 24 | class Solution(object): 25 | def generateMatrix(self, n): 26 | list1 = [i for i in range(1,n**2+1)] 27 | res = [[0]*n for _ in range(n)] 28 | 29 | dx = [0,1,0,-1] 30 | dy = [1,0,-1,0] 31 | 32 | x = y = di = 0 33 | for i in range(n**2): 34 | res[x][y] = list1[i] 35 | 36 | nx = x + dx[di] 37 | ny = y + dy[di] 38 | 39 | if 0 <= nx < n and 0 <= ny < n and res[nx][ny] == 0: 40 | x,y = nx,ny 41 | else: 42 | di = (di+1)%4 43 | x,y = x + dx[di],y + dy[di] 44 | 45 | return res 46 | ``` -------------------------------------------------------------------------------- /python/617. 合并二叉树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 3 | 4 | 你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。 5 | 6 | ``` 7 | 示例 1: 8 | 9 | 输入: 10 | Tree 1 Tree 2 11 | 1 2 12 | / \ / \ 13 | 3 2 1 3 14 | / \ \ 15 | 5 4 7 16 | 输出: 17 | 合并后的树: 18 | 3 19 | / \ 20 | 4 5 21 | / \ \ 22 | 5 4 7 23 | 注意: 合并必须从两个树的根节点开始。 24 | ``` 25 | 26 | ### 解析1: 27 | 递归,对树的每个结点进行递归求解。判断t1,t2是否为空,返回不为空的一个,如果都为空就返回空。然后根节点相加,再对左子树和右子树递归调用。 28 | 29 | N:结点的总数目 30 | | |复杂度|大小|百分比| 31 | |--|--|--|--| 32 | |时间|$O(n)$|56 ms|100%| 33 | |空间|$O(1)$|12.8 MB|6.09%| 34 | 35 | 步骤: 36 | 1. 判断根节点是否为空,返回不为空的根节点; 37 | 2. 统一都加在t1树上面,最后返回t1树; 38 | 3. 根节点值相加; 39 | 4. 对左子树和右子树分别递归调用函数; 40 | 41 | 42 | ```python 43 | # Definition for a binary tree node. 44 | # class TreeNode(object): 45 | # def __init__(self, x): 46 | # self.val = x 47 | # self.left = None 48 | # self.right = None 49 | 50 | class Solution(object): 51 | def mergeTrees(self, t1, t2): 52 | if not t1: 53 | return t2 54 | if not t2: 55 | return t1 56 | 57 | t1.val += t2.val 58 | t1.left = self.mergeTrees(t1.left, t2.left) 59 | t1.right = self.mergeTrees(t1.right, t2.right) 60 | 61 | return t1 62 | ``` -------------------------------------------------------------------------------- /python/62. 不同路径.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 3 | 4 | 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 5 | 6 | 问总共有多少条不同的路径? 7 | 8 | ``` 9 | 输入: m = 3, n = 2 10 | 输出: 3 11 | 解释: 12 | 从左上角开始,总共有 3 条路径可以到达右下角。 13 | 1. 向右 -> 向右 -> 向下 14 | 2. 向右 -> 向下 -> 向右 15 | 3. 向下 -> 向右 -> 向右 16 | ``` 17 | 18 | ### 解析1: 19 | 一共有m+n-2步,从左上角到右下角一定,一定向下走m-1步,向右走n-1步,区别在向下向右的顺序。由排列组合可知一共有$C_{m+n-2}^{m-1}$。最美的果然是数学啊。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(1)$|20 ms|83.55%| 24 | |空间|$O(1)$|11.7 MB|37.16%| 25 | 26 | ```python 27 | from math import factorial 28 | class Solution(object): 29 | def uniquePaths(self, m, n): 30 | return factorial(m+n-2)/(factorial(m-1)*factorial(n-1)) 31 | ``` 32 | 33 | ### 解析2: 34 | 动态规划需要二次遍历,明显慢于使用排列组合。 35 | 36 | | |复杂度|大小|百分比| 37 | |--|--|--|--| 38 | |时间|$O(mn)$|20 ms|83.55%| 39 | |空间|$O(mn)$|11.7 MB|37.16%| 40 | 41 | 状态含义:dp[i][j] 到达 i, j的最多路径; 42 | 状态方程:dp[i][j] = dp[i-1][j] + dp[i][j-1] 43 | 初始值:dp[0][:]第一行都为1,dp[:][0]第一列都为0; 44 | 45 | 46 | ```python 47 | class Solution(object): 48 | def uniquePaths(self, m, n): 49 | dp = [[1]*n] + [[1]+[0]*(n-1) for _ in range(m-1)] 50 | for i in range(1, m): 51 | for j in range(1, n): 52 | dp[i][j] = dp[i-1][j] + dp[i][j-1] 53 | return dp[-1][-1] 54 | ``` 55 | -------------------------------------------------------------------------------- /python/63. 不同路径 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 3 | 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 4 | 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? 5 | 6 | ```python 7 | 示例 1: 8 | 9 | 输入: 10 | [ 11 |   [0,0,0], 12 |   [0,1,0], 13 |   [0,0,0] 14 | ] 15 | 输出: 2 16 | 解释: 17 | 3x3 网格的正中间有一个障碍物。 18 | 从左上角到右下角一共有 2 条不同的路径: 19 | 1. 向右 -> 向右 -> 向下 -> 向下 20 | 2. 向下 -> 向下 -> 向右 -> 向右 21 | ``` 22 | 23 | ### 解析1: 24 | 动态规划,需要考虑障碍物的存在,有障碍物存在的地方,将DP值设置为0,其他仍然满足dp的转移方程。 25 | 26 | | |复杂度|大小|百分比| 27 | |--|--|--|--| 28 | |时间|$O(mn)$|44 ms|63.00%| 29 | |空间|$O(mn)$|11.8 MB|36.58%| 30 | 31 | 状态含义:dp[i][j] 到达 i, j的最多路径; 32 | 状态方程:dp[i][j] = dp[i-1][j] + dp[i][j-1] 无障碍物的点;0 有障碍物的点; 33 | 初始值:dp[0][:]第一行都为1,如果有障碍物存在后面都是0,dp[:][0]第一列都为0,障碍物同理; 34 | 35 | ```python 36 | class Solution(object): 37 | def uniquePathsWithObstacles(self, obstacleGrid): 38 | m,n = len(obstacleGrid),len(obstacleGrid[0]) 39 | 40 | if obstacleGrid[0][0] == 1: 41 | return 0 42 | obstacleGrid[0][0] = 1 43 | for i in range(1,m): 44 | obstacleGrid[i][0] = int(obstacleGrid[i][0] == 0 and obstacleGrid[i-1][0] == 1) 45 | for j in range(1,n): 46 | obstacleGrid[0][j] = int(obstacleGrid[0][j] == 0 and obstacleGrid[0][j-1] == 1) 47 | 48 | for i in range(1, m): 49 | for j in range(1, n): 50 | if obstacleGrid[i][j] == 0: 51 | obstacleGrid[i][j] = obstacleGrid[i-1][j] + obstacleGrid[i][j-1] 52 | else: 53 | obstacleGrid[i][j] = 0 54 | return obstacleGrid[m-1][n-1] 55 | ``` 56 | -------------------------------------------------------------------------------- /python/64. 最小路径和.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 3 | 4 | 说明:每次只能向下或者向右移动一步。、 5 | 6 | ``` 7 | 示例: 8 | 9 | 输入: 10 | [ 11 |   [1,3,1], 12 | [1,5,1], 13 | [4,2,1] 14 | ] 15 | 输出: 7 16 | 解释: 因为路径 1→3→1→1→1 的总和最小。 17 | ``` 18 | 19 | ### 解析1: 20 | 动态规划,明显的前后迭代关系。 21 | 状态含义: dp[i][j] 到达 [i,j]两点最小的路径和; 22 | 转移方程: dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j] 23 | 24 | | |复杂度|大小|百分比| 25 | |--|--|--|--| 26 | |时间|$O(mn)$|116 ms|81.59%| 27 | |空间|$O(mn)$|16.6 MB|5.12%| 28 | 29 | ```python 30 | class Solution: 31 | def minPathSum(self, grid: List[List[int]]) -> int: 32 | m,n = len(grid),len(grid[0]) 33 | 34 | dp = [[grid[0][0]]*n for _ in range(m)] 35 | for i in range(1,n): 36 | dp[0][i] = dp[0][i-1] + grid[0][i] 37 | 38 | for j in range(1,m): 39 | dp[j][0] = dp[j-1][0] + grid[j][0] 40 | 41 | for i in range(1, m): 42 | for j in range(1, n): 43 | dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j] 44 | return dp[-1][-1] 45 | ``` -------------------------------------------------------------------------------- /python/647. 回文子串.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。 3 | 4 | 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。 5 | 6 | ``` 7 | 示例 1: 8 | 9 | 输入: "abc" 10 | 输出: 3 11 | 解释: 三个回文子串: "a", "b", "c". 12 | ``` 13 | ### 解析1: 14 | 暴力法:生成每一个子串,然后判断是否是回文的,如果是回文的就加1. 15 | 16 | | |复杂度|大小|百分比| 17 | |--|--|--|--| 18 | |时间|$O(n^3)$|1488 ms|5.20%| 19 | |空间|$O(1)$|11.8 MB|59.09%| 20 | 21 | ```python 22 | class Solution(object): 23 | def countSubstrings(self, s): 24 | n = len(s) 25 | res = 0 26 | for i in range(n): 27 | for j in range(i, n): 28 | temp_str = s[i:j+1] 29 | if self.IsPalin(temp_str): 30 | res += 1 31 | return res 32 | 33 | def IsPalin(self, temp_str): 34 | # 判断一个字符串是否是回文 35 | return temp_str == temp_str[::-1] 36 | ``` 37 | 38 | ### 解析2: 39 | 中心扩散法,遍历每一个字符,以当前字符为回文子串的中心来进行扩散,产生一个新的回文子串就res++。 40 | 41 | | |复杂度|大小|百分比| 42 | |--|--|--|--| 43 | |时间|$O(n^2)$|116 ms|80.26%| 44 | |空间|$O(1)$|11.6 MB|64.77%| 45 | 46 | 步骤: 47 | 1. 遍历每一个字符; 48 | 1. 以当前字符i为中心,进行扩散,奇数扩散,直到不满足条件; 49 | 2. 以i,i+1为中心,进行扩散,偶数扩散; 50 | 51 | ```python 52 | class Solution(object): 53 | def countSubstrings(self, s): 54 | res,n = 0,len(s) 55 | 56 | for i in range(n): 57 | res += self.helper(s, i, i) 58 | res += self.helper(s, i, i+1) 59 | return res 60 | 61 | def helper(self,s, left, right): 62 | res = 0 63 | while left >= 0 and right < len(s) and s[left] == s[right]: 64 | res += 1 65 | left -= 1 66 | right += 1 67 | return res 68 | ``` -------------------------------------------------------------------------------- /python/654. 最大二叉树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下: 3 | 4 | 二叉树的根是数组中的最大元素。 5 | 左子树是通过数组中最大值左边部分构造出的最大二叉树。 6 | 右子树是通过数组中最大值右边部分构造出的最大二叉树。 7 | 通过给定的数组构建最大二叉树,并且输出这个树的根节点。 8 | 9 | ``` 10 | 示例 : 11 | 12 | 输入:[3,2,1,6,0,5] 13 | 输出:返回下面这棵树的根节点: 14 | 15 | 6 16 | / \ 17 | 3 5 18 | \ / 19 | 2 0 20 | \ 21 | 1 22 | ``` 23 | 24 | ### 解析1: 25 | 直接递归调用,每次提取当前数组的最大元素,然后获取其索引,对最大元素左边的和右边的分别递归调用函数。 26 | 27 | | |复杂度|大小|百分比| 28 | |--|--|--|--| 29 | |时间|$O(n)$|372 ms|19.85%| 30 | |空间|$O(n)$|14 MB|5.00%| 31 | 32 | ```python 33 | class Solution: 34 | def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode: 35 | if not nums: 36 | return None 37 | 38 | maxnum = max(nums) 39 | maxindex = nums.index(maxnum) 40 | 41 | root = TreeNode(maxnum) 42 | 43 | root.left = self.constructMaximumBinaryTree(nums[:maxindex]) 44 | root.right = self.constructMaximumBinaryTree(nums[maxindex+1:]) 45 | return root 46 | ``` 47 | 通过此方法,可以类似构建一个二叉查找树,构建左子树的时候,对小于根节点的数进行调用,反之对大于根节点的数进行递归调用。 48 | -------------------------------------------------------------------------------- /python/66. 加一.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 3 | 4 | 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 5 | 6 | 你可以假设除了整数 0 之外,这个整数不会以零开头。 7 | ``` 8 | 示例 1: 9 | 10 | 输入: [1,2,3] 11 | 输出: [1,2,4] 12 | 解释: 输入数组表示数字 123。 13 | 示例 2: 14 | 15 | 输入: [4,3,2,1] 16 | 输出: [4,3,2,2] 17 | 解释: 输入数组表示数字 4321。 18 | ``` 19 | 20 | ### 解析1: 21 | 直接转成字符串再操作。 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(n)$|48 ms|79.12%| 26 | |空间|$O(n)$|13.6 MB|5.64%| 27 | 28 | ```python 29 | class Solution: 30 | def plusOne(self, digits: List[int]) -> List[int]: 31 | num = int(''.join(map(str, digits)))+1 32 | num = list(str(num)) 33 | return map(int, num) 34 | ``` -------------------------------------------------------------------------------- /python/668. 乘法表中第k小的数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 几乎每一个人都用 乘法表。但是你能在乘法表中快速找到第k小的数字吗? 3 | 4 | 给定高度m 、宽度n 的一张 m * n的乘法表,以及正整数k,你需要返回表中第k 小的数字。 5 | 6 | ``` 7 | 例 1: 8 | 9 | 输入: m = 3, n = 3, k = 5 10 | 输出: 3 11 | 解释: 12 | 乘法表: 13 | 1 2 3 14 | 2 4 6 15 | 3 6 9 16 | 17 | 第5小的数字是 3 (1, 2, 2, 3, 3). 18 | ``` 19 | 20 | ### 解析1: 21 | 暴力法:获取表格的左右元素,然后排序,排序后返回第k个。 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(mn)$| || 26 | |空间|$O(mn)$| 超内存|| 27 | 28 | ```python 29 | class Solution(object): 30 | def findKthNumber(self, m, n, k): 31 | table = [i*j for i in range(1, m+1) for j in range(1, n+1)] 32 | table.sort() 33 | return table[k-1] 34 | ``` 35 | 36 | ### 解析2: 37 | 二分查找。遍历1到mn,如果有k个数小于这个数。 38 | ```python 39 | class Solution(object): 40 | def findKthNumber(self, m, n, k): 41 | def enough(x): 42 | count = 0 43 | for i in xrange(1, m+1): 44 | count += min(x // i, n) 45 | return count >= k 46 | 47 | lo, hi = 1, m * n 48 | while lo < hi: 49 | mi = (lo + hi) / 2 50 | if not enough(mi): 51 | lo = mi + 1 52 | else: 53 | hi = mi 54 | return lo 55 | ``` 56 | 57 | 参考资料:http://www.mamicode.com/info-detail-2445751.html -------------------------------------------------------------------------------- /python/69. x 的平方根.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 实现 int sqrt(int x) 函数。 3 | 4 | 计算并返回 x 的平方根,其中 x 是非负整数。 5 | 6 | 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 7 | ``` 8 | 示例 1: 9 | 10 | 输入: 4 11 | 输出: 2 12 | ``` 13 | 14 | ### 解析1: 15 | 暴力法,遍历求解是否出现 i**2 <= x and (i+1)**2 > x 16 | 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(n)$|超时|| 20 | |空间|$O(1)$| || 21 | 22 | ```python 23 | class Solution(object): 24 | def mySqrt(self, x): 25 | i = 0 26 | while i ** 2 <= x: 27 | if (i+1)**2 > x:return i 28 | i += 1 29 | ``` 30 | 31 | ### 解析2: 32 | 二分法,通过二分查找。主要是很多细节,中位数的选择,左右边界的选择。 33 | 34 | | |复杂度|大小|百分比| 35 | |--|--|--|--| 36 | |时间|$O(logn)$|28ms|70%| 37 | |空间|$O(1)$|11.7 MB|12.81%| 38 | 39 | ```python 40 | class Solution(object): 41 | def mySqrt(self, x): 42 | if x == 0:return 0 43 | left = 0 44 | right = 99999 45 | while left < right: 46 | mid = (left + right + 1)>>1 47 | if mid **2 > x:right = mid - 1 48 | else:left = mid 49 | return left 50 | ``` -------------------------------------------------------------------------------- /python/7. 整数反转.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: 123 7 | 输出: 321 8 |  示例 2: 9 | 10 | 输入: -123 11 | 输出: -321 12 | ``` 13 | 14 | ### 解析1: 15 | 直接翻转。 16 | 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(n)$|24 ms|78.46%| 20 | |空间|$O(n)$|11.7 MB|23.69%| 21 | 22 | 23 | ```python 24 | class Solution(object): 25 | def reverse(self, x): 26 | x = str(x) 27 | if x[0] == '-': 28 | res = int('-' + x[1:][::-1]) 29 | 30 | else: 31 | res = int(x[::-1]) 32 | print(res) 33 | return res if -1*2**31 <= res <= 2**31-1 else 0 34 | ``` 35 | -------------------------------------------------------------------------------- /python/70.爬楼梯.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 3 | 4 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 5 | 6 | 注意:给定 n 是一个正整数。 7 | 8 | >示例 1: 9 | 输入: 2 10 | 输出: 2 11 | 解释: 有两种方法可以爬到楼顶。 12 | 1. 1 阶 + 1 阶 13 | 2. 2 阶 14 | 15 | ### 解析1: 16 | 套用斐波那契数列。主要是起始值和相加的次数。 17 | 18 | 时间:O(n) 19 | 空间:O(1) 20 | 21 | ```python 22 | class Solution: 23 | def climbStairs(self, n: int) -> int: 24 | if n <= 1:return 1 25 | f1,f2 = 1,2 26 | for i in range(n-2): 27 | f2,f1 = f1+f2,f2 28 | return f2 29 | ``` 30 | 31 | 好吧,直接调用斐波那契公式的我给满分。 -------------------------------------------------------------------------------- /python/704. 二分查找.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: nums = [-1,0,3,5,9,12], target = 9 8 | 输出: 4 9 | 解释: 9 出现在 nums 中并且下标为 4 10 | ``` 11 | 12 | ### 解析1: 13 | 直接二分查找,没啥好说的。 14 | 15 | ```python 16 | class Solution(object): 17 | def search(self, nums, target): 18 | n = len(nums) 19 | left, right = 0,n-1 20 | while left <= right: 21 | mid = (left + right)//2 22 | if nums[mid] == target: 23 | return mid 24 | elif nums[mid] > target: 25 | right = mid-1 26 | elif nums[mid] < target: 27 | left = mid + 1 28 | return -1 29 | ``` 30 | 31 | 关于二分查找一个很好的总结:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/; 32 | https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/; -------------------------------------------------------------------------------- /python/714. 买卖股票的最佳时机含手续费.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。 3 | 4 | 你可以无限次地完成交易,但是你每次交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 5 | 6 | 返回获得利润的最大值。 7 | 8 | ``` 9 | 示例 1: 10 | 11 | 输入: prices = [1, 3, 2, 8, 4, 9], fee = 2 12 | 输出: 8 13 | 解释: 能够达到的最大利润: 14 | 在此处买入 prices[0] = 1 15 | 在此处卖出 prices[3] = 8 16 | 在此处买入 prices[4] = 4 17 | 在此处卖出 prices[5] = 9 18 | 总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8. 19 | 注意: 20 | 21 | 0 < prices.length <= 50000. 22 | 0 < prices[i] < 50000. 23 | 0 <= fee < 50000. 24 | ``` 25 | 26 | ### 解析1: 27 | 和122题类似,只是每次交易的时候需要考虑交易费,当卖出股票的时候减去交易费。不能再像122题那样使用贪心算法求解,因为有fee来控制,如果fee比较大,影响偏向于交易次数少的方案。 28 | 29 | **状态转移方程:** 30 | ``` 31 | dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i] - fee) 32 | dp0 = max(dp0, dp1+prices[i]-fee) 33 | 第i天未持有股票获得的最多利润 = max(第i-1天未持有股票获得的最多利润, 第i-1天持有股票获得的最多利润 卖掉 prices[i]) 34 | 35 | dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]) 36 | dp1 = max(dp1, dp0-prices[0]) 37 | 第i天持有股票获得的最多利润 = max(第i-1天持有股票获得的最多利润, 第i-1天未持有股票获得的最多利润 买入 prices[i]) 38 | ``` 39 | 40 | | |复杂度|大小|百分比| 41 | |--|--|--|--| 42 | |时间|$O(n)$|860 ms|70.9%| 43 | |空间|$O(1)$|17.7 MB|12.82%| 44 | 45 | 46 | ```python 47 | class Solution(object): 48 | def maxProfit(self, prices, fee): 49 | if not prices: 50 | return 0 51 | dp0,dp1 = 0, -prices[0] 52 | 53 | length = len(prices) 54 | for i in range(1, length): 55 | temp = dp0 56 | dp0 = max(dp0, dp1 + prices[i] - fee) 57 | dp1 = max(dp1, temp - prices[i]) 58 | return dp0 59 | ``` -------------------------------------------------------------------------------- /python/72. 编辑距离.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。 3 | 4 | 你可以对一个单词进行如下三种操作: 5 | 6 | 插入一个字符 7 | 删除一个字符 8 | 替换一个字符 9 | ``` 10 | 示例 1: 11 | 12 | 输入: word1 = "horse", word2 = "ros" 13 | 输出: 3 14 | 解释: 15 | horse -> rorse (将 'h' 替换为 'r') 16 | rorse -> rose (删除 'r') 17 | rose -> ros (删除 'e') 18 | ``` 19 | 20 | ### 解析1: 21 | 动态规划。 22 | 23 | 状态含义:dp[i][j] word1的前i个单词 转换到 word2的前j个单词 所需的步数; 24 | 状态方程: dp[i][j] = dp[i-1][j-1] 如果word1[i]=word2[j],即这两个单词的位置相等的话; 25 | dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1, dp[i-1][j-1]对应替换操作,将word1[i]替换成word2[j],dp[i-1][j]对应删除操作,将word1[i]删除掉, dp[i][j-1]对应插入操作。 26 | 27 | | |复杂度|大小|百分比| 28 | |--|--|--|--| 29 | |时间|$O(n)$|236 ms|61.98%| 30 | |空间|$O(n)$|17.7 MB|5.38%| 31 | 32 | ```python 33 | class Solution: 34 | def minDistance(self, word1: str, word2: str) -> int: 35 | n1,n2 = len(word1),len(word2) 36 | 37 | dp = [[0]*(n2+1) for _ in range(n1+1)] 38 | 39 | for i in range(n2): 40 | dp[0][i+1] = dp[0][i] + 1 41 | for i in range(n1): 42 | dp[i+1][0] = dp[i][0] + 1 43 | 44 | for i in range(1, n1+1): 45 | for j in range(1, n2+1): 46 | if word1[i-1] == word2[j-1]: 47 | dp[i][j] = dp[i-1][j-1] 48 | else: 49 | dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1 50 | return dp[-1][-1] 51 | ``` -------------------------------------------------------------------------------- /python/739. 每日温度.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。 3 | 4 | 例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 5 | 6 | 提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。 7 | 8 | ### 解析1: 9 | 两次遍历数组,然后判断后面最近的大于当前数的索引,注意如果不存在返回0,最后要加一个判断。暴力法,超时。 10 | 11 | | |复杂度|大小|百分比| 12 | |--|--|--|--| 13 | |时间|$O(n^2)$|超时|| 14 | |空间|$O(n)$||| 15 | 16 | ```python 17 | class Solution(object): 18 | def dailyTemperatures(self, T): 19 | 20 | res = [] 21 | n = len(T) 22 | for i in range(n-1): 23 | temp = 1 24 | for j in range(i+1, n): 25 | if T[i] < T[j]: 26 | break 27 | else: 28 | temp += 1 29 | if j == n-1 and T[j] <= T[i]: 30 | temp = 0 31 | res.append(temp) 32 | 33 | return res + [0] 34 | ``` 35 | 36 | ### 解析2: 37 | 用一个栈保存温度的索引,如果当前元素大于栈顶元素的温度,那么当前元素是栈顶元素对应的下一个上升温度。如果不大于,就将其入栈,直到找到大于栈顶的元素。 38 | 39 | | |复杂度|大小|百分比| 40 | |--|--|--|--| 41 | |时间|$O(n)$|520ms|87.89%| 42 | |空间|$O(n)$|15MB|38.89%| 43 | 44 | 45 | 步骤: 46 | 1. 建立一个站; 47 | 2. 遍历温度数组; 48 | 1. 如果stack不为空,并且栈顶元素温度小于当前的遍历元素值,则出栈,此时索引距离差即天数差; 49 | 2. 否则入栈 50 | 51 | ```python 52 | class Solution(object): 53 | def dailyTemperatures(self, T): 54 | stack,res = [],[0]*len(T) 55 | for i,v in enumerate(T): 56 | while stack and T[stack[-1]] < v: 57 | res[stack.pop()] = i - stack[-1] 58 | stack.append(i) 59 | return res 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /python/746. 使用最小花费爬楼梯.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i](索引从0开始)。 3 | 4 | 每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。 5 | 6 | 您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。 7 | ``` 8 | 示例 1: 9 | 10 | 输入: cost = [10, 15, 20] 11 | 输出: 15 12 | 解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。 13 | ``` 14 | 15 | ### 解析1: 16 | 动态规划,关键依然是如何建立状态转移方程。 17 | 18 | 状态含义:dp[i]到达第n台阶所需要的最小花费; 19 | 状态转移方程:dp[i] = min(dp[i-1],dp[i-2]) + cost[i]; 20 | 到达第i个台阶,有两种方式,前一个或者前两个台阶跳过去,比较其大小,然后加cost[i]。 21 | 到达楼顶有两种方式,跳到第n台阶,或者第n-1个台阶,所以最后要输出min(dp[n-1],dp[n-2]) 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(n)$|760 ms|10.75%| 26 | |空间|$O(1)$|19 MB|50.62%| 27 | 28 | ```python 29 | class Solution: 30 | def minCostClimbingStairs(self, cost: List[int]) -> int: 31 | n = len(cost) 32 | 33 | if n == 0: 34 | return 0 35 | elif n == 1: 36 | return cost[0] 37 | 38 | dp = [0]*(n) 39 | dp[0] = cost[0] 40 | dp[1] = cost[1] 41 | 42 | for i in range(2, n): 43 | dp[i] = min(dp[i-1],dp[i-2]) + cost[i] 44 | return min(dp[n-1],dp[n-2]) 45 | ``` -------------------------------------------------------------------------------- /python/75. 颜色分类.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 3 | 4 | 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 5 | ``` 6 | 注意: 7 | 不能使用代码库中的排序函数来解决这道题。 8 | 9 | 示例: 10 | 11 | 输入: [2,0,2,1,1,0] 12 | 输出: [0,0,1,1,2,2] 13 | ``` 14 | 15 | ### 解析1: 16 | 两次扫描第一次将全部的0移动到前面,第二次从0的下一位开始遍历,将所有的1移动到前面。 17 | 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(n)$|72 ms|19.09%| 21 | |空间|$O(1)$|12.1 MB|5.21%| 22 | 23 | ```python 24 | class Solution: 25 | def sortColors(self, nums: List[int]) -> None: 26 | n = len(nums) 27 | zero = 0 28 | i = 0 29 | while i < n: 30 | if nums[i] == 0: 31 | nums[i],nums[zero] = nums[zero],nums[i] 32 | zero += 1 33 | i +=1 34 | i = one = zero 35 | while i < n: 36 | if nums[i] == 1: 37 | nums[i],nums[one] = nums[one],nums[i] 38 | one += 1 39 | i += 1 40 | 41 | ``` 42 | 43 | ### 解析2: 44 | 方法类似,修改为一次遍历。 45 | 46 | | |复杂度|大小|百分比| 47 | |--|--|--|--| 48 | |时间|$O(n)$|56 ms|51.07%| 49 | |空间|$O(1)$|13.8 MB|5.21%| 50 | 51 | ```Python 52 | class Solution: 53 | def sortColors(self, nums: List[int]) -> None: 54 | ''' 55 | 荷兰三色旗问题解 56 | ''' 57 | # 对于所有 idx < p0 : nums[idx < p0] = 0 58 | # curr是当前考虑元素的下标 59 | p0 = curr = 0 60 | # 对于所有 idx > p2 : nums[idx > p2] = 2 61 | p2 = len(nums) - 1 62 | 63 | while curr <= p2: 64 | if nums[curr] == 0: 65 | nums[p0], nums[curr] = nums[curr], nums[p0] 66 | p0 += 1 67 | curr += 1 68 | elif nums[curr] == 2: 69 | nums[curr], nums[p2] = nums[p2], nums[curr] 70 | p2 -= 1 71 | else: 72 | curr += 1 73 | ``` -------------------------------------------------------------------------------- /python/77. 组合.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 3 | 4 | ``` 5 | 示例: 6 | 7 | 输入: n = 4, k = 2 8 | 输出: 9 | [ 10 | [2,4], 11 | [3,4], 12 | [2,3], 13 | [1,2], 14 | [1,3], 15 | [1,4], 16 | ] 17 | ``` 18 | 19 | ### 解析1: 20 | 和17题非常相似,回溯法即可,两个参数,一个是temp,一个是索引。每次更换备选空间。 21 | 22 | | |复杂度|大小|百分比| 23 | |--|--|--|--| 24 | |时间| |744 ms|33.99%| 25 | |空间|$O(n)$|13 MB|41.63%| 26 | 27 | ```python 28 | class Solution(object): 29 | def combine(self, n, k): 30 | res = [] 31 | 32 | def backtrack(index,temp): 33 | if len(temp) == k: 34 | res.append(temp) 35 | for i in range(index, n+1): 36 | backtrack(i+1, temp+[i]) 37 | 38 | backtrack(1, []) 39 | return res 40 | ``` 41 | 42 | ### 解析2: 43 | 44 | 使用索引来进行修改,改为备选数组。 45 | 46 | | |复杂度|大小|百分比| 47 | |--|--|--|--| 48 | |时间| |712 ms|33.99%| 49 | |空间|$O(n)$|13.1 MB|41.63%| 50 | 51 | 52 | ```python 53 | class Solution(object): 54 | def combine(self, n, k): 55 | res = [] 56 | nums = list(range(1,n+1)) 57 | 58 | def backtrack(nums, temp): 59 | if len(temp) == k: 60 | res.append(temp) 61 | 62 | for i in range(len(nums)): 63 | backtrack(nums[i+1:], temp+[nums[i]]) 64 | 65 | backtrack(nums, []) 66 | return res 67 | ``` -------------------------------------------------------------------------------- /python/78.子集.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 3 | 4 | 说明:解集不能包含重复的子集。 5 | 6 | 示例: 7 | 8 | 输入: nums = [1,2,3] 9 | 输出: 10 | [ 11 | [3], 12 |   [1], 13 |   [2], 14 |   [1,2,3], 15 |   [1,3], 16 |   [2,3], 17 |   [1,2], 18 |   [] 19 | ] 20 | 21 | ### 解析1: 22 | 回溯法,和47.全排列差不多,只不过是选过的元素不能再选了,只能再添加没有选过的元素。同时判断,如果新子集不在res里面才添加,否则不添加。 23 | 24 | | |复杂度|大小|百分比| 25 | |--|--|--|--| 26 | |时间||60 ms|41.88%| 27 | |空间| |12 MB|21.29%| 28 | 29 | ```python 30 | class Solution(object): 31 | def subsets(self, nums): 32 | """ 33 | :type nums: List[int] 34 | :rtype: List[List[int]] 35 | """ 36 | res = [] 37 | def backtrack(nums, temp): 38 | if temp not in res: 39 | res.append(temp) 40 | for i in range(len(nums)): 41 | backtrack(nums[i+1:], temp+[nums[i]]) 42 | backtrack(nums, []) 43 | return res 44 | ``` 45 | 46 | ### 解析2: 47 | 采用库函数:`itertools.combinations`。 48 | 49 | | |复杂度|大小|百分比| 50 | |--|--|--|--| 51 | |时间||40 ms|99.73%| 52 | |空间| |12 MB|22.36%| 53 | 54 | ```python 55 | import itertools 56 | class Solution(object): 57 | def subsets(self, nums): 58 | res = [] 59 | for i in range(len(nums)+1): 60 | res.extend(itertools.combinations(nums, i)) 61 | return res 62 | ``` 63 | 64 | ### 解析3: 65 | 迭代法,即不断循环。设n个数产生的子集$f(n)$,前n-1个数产生子集$f(n-1)$,则$f(n) = f(n-1) + [x+new for x in f(n-1)]$,即$f(n-1)$内的每一个元素加上新的元素。 66 | 67 | | |复杂度|大小|百分比| 68 | |--|--|--|--| 69 | |时间| |80 ms|15.02%| 70 | |空间| |14 MB|5.27%| 71 | 72 | ```python 73 | class Solution: 74 | def subsets(self, nums: List[int]) -> List[List[int]]: 75 | res = [[]] 76 | for i in nums: 77 | res = res + [num+[i] for num in res] 78 | return res 79 | ``` -------------------------------------------------------------------------------- /python/85. 最大矩形.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 3 | 4 | ``` 5 | 示例: 6 | 7 | 输入: 8 | [ 9 | ["1","0","1","0","0"], 10 | ["1","0","1","1","1"], 11 | ["1","1","1","1","1"], 12 | ["1","0","0","1","0"] 13 | ] 14 | 输出: 6 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /python/876. 链表的中间结点.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。 3 | 4 | 如果有两个中间结点,则返回第二个中间结点。 5 | 6 |   7 | ``` 8 | 示例 1: 9 | 10 | 输入:[1,2,3,4,5] 11 | 输出:此列表中的结点 3 (序列化形式:[3,4,5]) 12 | 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。 13 | 注意,我们返回了一个 ListNode 类型的对象 ans,这样: 14 | ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. 15 | ``` 16 | 17 | ### 解析1: 18 | 双指针,快慢指针。快指针一次走两步,满指针 19 | 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|24 ms|50.34%| 23 | |空间|$O(n)$|11.8 MB|33.78%| 24 | 25 | ```python 26 | class Solution(object): 27 | def middleNode(self, head): 28 | fast = head 29 | slow = head 30 | 31 | while fast and fast.next: 32 | fast = fast.next.next 33 | slow = slow.next 34 | return slow 35 | ``` -------------------------------------------------------------------------------- /python/88. 合并两个有序数组.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。 3 | 4 | 说明: 5 | 6 | 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 7 | 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 8 | ``` 9 | 示例: 10 | 11 | 输入: 12 | nums1 = [1,2,3,0,0,0], m = 3 13 | nums2 = [2,5,6], n = 3 14 | 15 | 输出: [1,2,2,3,5,6] 16 | ``` 17 | 18 | ### 解析1: 19 | 排序后赋值在nums1中。对原nums1进行修改。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O((m+n)log(m+n))$|28 ms|81.99%| 24 | |空间|$O(m+n)$|11.7 MB|25.63%| 25 | 26 | ```python 27 | class Solution(object): 28 | def merge(self, nums1, m, nums2, n): 29 | nums1[:] = sorted(nums1[:m] + nums2) 30 | ``` 31 | 32 | ### 解析2: 33 | 归并思路。将nums1复制,然后依次和nums2比较,选择较小的,放在nums1里面。 34 | 35 | | |复杂度|大小|百分比| 36 | |--|--|--|--| 37 | |时间|$O(m+n)$|24 ms|93.99%| 38 | |空间|$O(m+n)$|11.8 MB|15.63%| 39 | 40 | ```python 41 | class Solution(object): 42 | def merge(self, nums1, m, nums2, n): 43 | nums1_copy = nums1[:m] 44 | nums1[:] = [] 45 | p1,p2 = 0,0 46 | while p1 < m and p2 < n: 47 | if nums1_copy[p1] <= nums2[p2]: 48 | nums1.append(nums1_copy[p1]) 49 | p1 += 1 50 | else: 51 | nums1.append(nums2[p2]) 52 | p2 += 1 53 | if p1 < m: 54 | nums1[p1+p2:] = nums1_copy[p1:] 55 | if p2 < n: 56 | nums1[p1+p2:] = nums2[p2:] 57 | ``` -------------------------------------------------------------------------------- /python/9. 回文数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: 121 7 | 输出: true 8 | 9 | 示例 2: 10 | 11 | 输入: -121 12 | 输出: false 13 | 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 14 | ``` 15 | 16 | ### 解析1: 17 | 转成字符串直接翻转,然后判断是否相等。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(n)$|72 ms|48.46%| 22 | |空间|$O(n)$|11.6 MB|33.69%| 23 | 24 | 25 | ```python 26 | class Solution(object): 27 | def isPalindrome(self, x): 28 | x = str(x) 29 | return x == x[::-1] 30 | ``` -------------------------------------------------------------------------------- /python/90. 子集 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 3 | 4 | 说明:解集不能包含重复的子集。 5 | ``` 6 | 示例: 7 | 8 | 输入: [1,2,2] 9 | 输出: 10 | [ 11 | [2], 12 | [1], 13 | [1,2,2], 14 | [2,2], 15 | [1,2], 16 | [] 17 | ] 18 | ``` 19 | 20 | ### 解析1: 21 | 回溯法,套用模板。 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O()$|32 ms|74.57%| 26 | |空间|$O(1)$|12 MB|18.85%| 27 | 28 | ```python 29 | class Solution(object): 30 | def subsetsWithDup(self, nums): 31 | nums = sorted(nums) 32 | res = [] 33 | 34 | def backtrack(nums, temp): 35 | if temp not in res: 36 | res.append(temp) 37 | 38 | for i in range(len(nums)): 39 | if i>0 and nums[i] == nums[i-1]: 40 | continue 41 | else: 42 | backtrack(nums[i+1:], temp+[nums[i]]) 43 | 44 | backtrack(nums, []) 45 | return res 46 | ``` -------------------------------------------------------------------------------- /python/92. 反转链表 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。 3 | 4 | 说明: 5 | 1 ≤ m ≤ n ≤ 链表长度。 6 | ``` 7 | 示例: 8 | 9 | 输入: 1->2->3->4->5->NULL, m = 2, n = 4 10 | 输出: 1->4->3->2->5->NULL 11 | ``` 12 | 13 | ### 解析1: 14 | 链表未到达要翻转的地方之前,类似链表遍历,向前移动即可。遇到反转的起点,翻转链表,直到反转结束点。对指针的要求比较高。 15 | 16 | | |复杂度|大小|百分比| 17 | |--|--|--|--| 18 | |时间|$O(n)$|20 ms|84.42%| 19 | |空间|$O(n)$|12 MB|18.93%| 20 | 21 | ```python 22 | class Solution: 23 | def reverseBetween(self, head, m, n): 24 | # Empty list 25 | if not head: 26 | return None 27 | 28 | # Move the two pointers until they reach the proper starting point 29 | # in the list. 30 | cur, prev = head, None 31 | 32 | while m > 1: 33 | prev = cur 34 | cur = cur.next 35 | m, n = m - 1, n - 1 36 | 37 | # The two pointers that will fix the final connections. 38 | tail, con = cur, prev 39 | 40 | # Iteratively reverse the nodes until n becomes 0. 41 | while n: 42 | third = cur.next 43 | cur.next = prev 44 | prev = cur 45 | cur = third 46 | n -= 1 47 | 48 | # Adjust the final connections as explained in the algorithm 49 | if con: 50 | con.next = prev 51 | else: 52 | head = prev 53 | tail.next = cur 54 | return head 55 | 56 | ``` 57 | -------------------------------------------------------------------------------- /python/94.二叉树的中序遍历.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,返回它的中序 遍历。 3 | 4 | ``` 5 | 示例: 6 | 输入: [1,null,2,3] 7 | 1 8 | \ 9 | 2 10 | / 11 | 3 12 | 13 | 输出: [1,3,2] 14 | ``` 15 | 16 | ### 解析1: 17 | DFS没啥好说的,递归遍历,先左子树,然后根结点,然后右子树。递归的代码写起来更简单一些。 18 | 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(n)$|36 ms|7.64%| 22 | |空间|$O(n)$|11.8 MB|23.52%| 23 | 24 | 25 | ```python 26 | class Solution(object): 27 | def inorderTraversal(self, root): 28 | res = [] 29 | def dfs(root): 30 | if not root: 31 | return None 32 | dfs(root.left) 33 | res.append(root.val) 34 | dfs(root.right) 35 | dfs(root) 36 | return res 37 | ``` 38 | 39 | ### 解析2: 40 | 迭代遍历,用栈依次保存结点。 41 | 42 | | |复杂度|大小|百分比| 43 | |--|--|--|--| 44 | |时间|$O(n)$|24 ms|60.07%| 45 | |空间|$O(n)$|11.9 MB|6.41%| 46 | 47 | ```python 48 | class Solution: 49 | def inorderTraversal(self, root): 50 | res = [] 51 | stack = [] 52 | # 用p当做指针 53 | p = root 54 | while p or stack: 55 | # 把左子树压入栈中 56 | while p: 57 | stack.append(p) 58 | p = p.left 59 | # 输出 栈顶元素 60 | tmp = stack.pop() 61 | res.append(tmp.val) 62 | # 看右子树 63 | p = tmp.right 64 | return res 65 | ``` 66 | 67 | 68 | ### 知识点: 69 | 树的遍历: 70 | 深度优先DFS: 71 | * 前序遍历:根左右(Leetcode144) 72 | * 中序遍历:左根右(Leetcode94) 73 | * 后序遍历:左右根(Leetcode145) 74 | 75 | 广度优先BFS 76 | 77 | -------------------------------------------------------------------------------- /python/96. 不同的二叉搜索树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 3 | ``` 4 | 示例: 5 | 6 | 输入: 3 7 | 输出: 5 8 | 解释: 9 | 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 10 | 11 | 1 3 3 2 1 12 | \ / / / \ \ 13 | 3 2 1 1 3 2 14 | / / \ \ 15 | 2 1 2 3 16 | ``` 17 | 18 | ### 解析1: 19 | 公式法,直接推到出具体的公式:$C_0 = 1,C_{n+1} = \frac{2(2n+1)}{n+2}C_n$ 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|24 ms|53.42%| 24 | |空间|$O(N!)$|11.8 MB|29.63%| 25 | 26 | ```python 27 | class Solution(object): 28 | def numTrees(self, n): 29 | res = 1 30 | for i in range(n): 31 | res = res * 2*(2*i+1)/(i+2) 32 | return int(res) 33 | ``` -------------------------------------------------------------------------------- /python/98. 验证二叉搜索树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 3 | 4 | 假设一个二叉搜索树具有如下特征: 5 | 6 | 节点的左子树只包含小于当前节点的数。 7 | 节点的右子树只包含大于当前节点的数。 8 | 所有左子树和右子树自身必须也是二叉搜索树。 9 | 10 | ``` 11 | 示例 1: 12 | 13 | 输入: 14 | 2 15 | / \ 16 | 1 3 17 | 输出: true 18 | ``` 19 | 20 | ### 解析1: 21 | 中序遍历,二叉搜索树的中序遍历将会得到一个递增的序列。对二叉树做中序遍历,判断是否是递增序列即可。 22 | 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(nlogn)$|48 ms|48.21%| 26 | |空间|$O(n)$|17.5 MB|5.07%| 27 | 28 | ```python 29 | class Solution(object): 30 | def isValidBST(self, root): 31 | res = [] 32 | 33 | def dfs(root): 34 | if not root:return None 35 | dfs(root.left) 36 | res.append(root.val) 37 | dfs(root.right) 38 | 39 | dfs(root) 40 | return res == sorted(list(set(res))) 41 | ``` 42 | 43 | ### 解析2: 44 | 递归。依次遍历 45 | 46 | | |复杂度|大小|百分比| 47 | |--|--|--|--| 48 | |时间|$O(n)$|40 ms|42.21%| 49 | |空间|$O(n)$|17.4 MB|5.07%| 50 | 51 | ```python 52 | class Solution(object): 53 | def isValidBST(self, root): 54 | def helper(node, lower=float('-inf'), higher = float('inf')): 55 | if not node:return True 56 | val = node.val 57 | if val <= lower or val >= higher: 58 | return False 59 | if not helper(node.left, lower, val): 60 | return False 61 | if not helper(node.right, val, higher): 62 | return False 63 | return True 64 | 65 | return helper(root) 66 | ``` -------------------------------------------------------------------------------- /python/994. 腐烂的橘子.md: -------------------------------------------------------------------------------- 1 | ### 题目; 2 | 在给定的网格中,每个单元格可以有以下三个值之一: 3 | 4 | 值 0 代表空单元格; 5 | 值 1 代表新鲜橘子; 6 | 值 2 代表腐烂的橘子。 7 | 每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。 8 | 9 | 返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。 10 | ``` 11 | 输入:[[2,1,1],[1,1,0],[0,1,1]] 12 | 输出:4 13 | 示例 2: 14 | 15 | 输入:[[2,1,1],[0,1,1],[1,0,1]] 16 | 输出:-1 17 | 解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个正向上。 18 | ``` 19 | ### 解析1: 20 | * **算法流程:** 21 | * 1.将新鲜和腐烂的分开放在两个set中; 22 | * 2.对腐烂的进行遍历,将他的相邻位置全部变成腐烂的; 23 | * 3.更新腐烂的集合为新腐烂的橘子,同时新鲜的减去腐烂的这些; 24 | * 4.如果新腐烂的在fresh之前变成空集,则返回-1; 25 | 26 | 27 | * **复杂度:** 28 | 29 | | |复杂度|大小|百分比| 30 | |--|--|--|--| 31 | |时间|$O(mn)$|60 ms|57.90%| 32 | |空间|$O(mn)$|13.1 MB|17.65%| 33 | 34 | ```python 35 | class Solution: 36 | def orangesRotting(self, grid: List[List[int]]) -> int: 37 | m,n = len(grid),len(grid[0]) 38 | 39 | fresh = {(i,j) for i in range(m) for j in range(n) if grid[i][j]==1} 40 | rotten = {(i,j) for i in range(m) for j in range(n) if grid[i][j]==2} 41 | time = 0 42 | 43 | while fresh: 44 | if not rotten:return -1 45 | rotten = {(i+dx,j+dy) for i,j in rotten for dx,dy in [(0,1),(0,-1),(1,0),(-1,0)] if (i+dx,j+dy) in fresh} # 一轮后新的rotten,更新才可以 46 | fresh -= rotten 47 | time += 1 # 多个腐烂的同时开始像两遍腐烂 48 | 49 | return time 50 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题04. 二维数组中的查找.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 3 | 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 4 | 5 |   6 | ``` 7 | 示例: 8 | 9 | 现有矩阵 matrix 如下: 10 | 11 | [ 12 | [1, 4, 7, 11, 15], 13 | [2, 5, 8, 12, 19], 14 | [3, 6, 9, 16, 22], 15 | [10, 13, 14, 17, 24], 16 | [18, 21, 23, 26, 30] 17 | ] 18 | 给定 target = 5,返回 true。 19 | 20 | 给定 target = 20,返回 false。 21 | ``` 22 | 23 | ### 解析1: 24 | * **算法思想:**完全遍历数组,没啥好说的 25 | 26 | * **复杂度:** 27 | | |复杂度|大小|百分比| 28 | |--|--|--|--| 29 | |时间|$O(nm)$|212 ms|40.67%| 30 | |空间|$O(1)$|19.6 MB|100%| 31 | 32 | ```python 33 | class Solution: 34 | def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool: 35 | n = len(matrix) 36 | if n > 0:m = len(matrix[0]) 37 | for i in range(n): 38 | for j in range(m): 39 | if matrix[i][j] == target: 40 | return True 41 | return False 42 | ``` 43 | 44 | ### 解析2: 45 | 从右上角开始查找。 46 | 47 | * **复杂度:** 48 | | |复杂度|大小|百分比| 49 | |--|--|--|--| 50 | |时间|$O(n+m)$|200 ms|72.67%| 51 | |空间|$O(1)$|19.6 MB|100%| 52 | 53 | ```python 54 | class Solution: 55 | def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool: 56 | n = len(matrix) 57 | if n > 0: 58 | m = len(matrix[0]) 59 | else: 60 | return False 61 | i,j = 0,m-1 62 | while i <= n-1 and j >= 0: 63 | if matrix[i][j] == target:return True 64 | elif matrix[i][j] > target: j -= 1 65 | else:i+=1 66 | 67 | return False 68 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题05. 替换空格.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入:s = "We are happy." 8 | 输出:"We%20are%20happy." 9 | ``` 10 | 11 | ### 解析1: 12 | 遍历,替换。 13 | 14 | * **复杂度:** 15 | | |复杂度|大小|百分比| 16 | |--|--|--|--| 17 | |时间|$O(n+m)$|28 ms|87.36%| 18 | |空间|$O(1)$|13.2 MB|100%| 19 | 20 | ```python 21 | class Solution: 22 | def replaceSpace(self, s: str) -> str: 23 | res = '' 24 | for c in s: 25 | if c == ' ': 26 | res += '%20' 27 | else: 28 | res += c 29 | return res 30 | ``` 31 | 32 | ### 解析2: 33 | python语法直接替换。 34 | 35 | * **复杂度:** 36 | | |复杂度|大小|百分比| 37 | |--|--|--|--| 38 | |时间|$O(n+m)$|28 ms|87.36%| 39 | |空间|$O(1)$|13.2 MB|100%| 40 | 41 | ```python 42 | class Solution: 43 | def replaceSpace(self, s: str) -> str: 44 | return s.replace(' ','%20') 45 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题06. 从尾到头打印链表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。 3 | ``` 4 | 示例 1: 5 | 6 | 输入:head = [1,3,2] 7 | 输出:[2,3,1] 8 | ``` 9 | 10 | ### 解析1: 11 | 非递归,使用栈。用栈保存链表结点值,然后逆序输出。 12 | 13 | * **复杂度:** 14 | | |复杂度|大小|百分比| 15 | |--|--|--|--| 16 | |时间|$O(n)$|40 ms|87.40%| 17 | |空间|$O(1)$|15.2 MB|100%| 18 | 19 | 20 | ```python 21 | class Solution: 22 | def reversePrint(self, head: ListNode) -> List[int]: 23 | res = [] 24 | while head: 25 | res.append(head.val) 26 | head = head.next 27 | return res[::-1] 28 | ``` 29 | 30 | ### 解析2: 31 | 回溯法。 32 | * **算法流程:** 33 | **递推阶段:** 每次传入 head.next ,以 head == None(即走过链表尾部节点)为递归终止条件,此时返回空列表 [] 。 34 | **回溯阶段:** 利用 Python 语言特性,递归回溯时每次返回 当前 list + 当前节点值 [head.val] ,即可实现节点的倒序输出。 35 | 36 | * **复杂度:** 37 | 38 | | |复杂度|大小|百分比| 39 | |--|--|--|--| 40 | |时间|$O(n)$|48 ms|52.85%| 41 | |空间|$O(1)$|22.1 MB|100%| 42 | 43 | 44 | ```python 45 | class Solution: 46 | def reversePrint(self, head: ListNode) -> List[int]: 47 | res = [] 48 | self.helper(head, res) 49 | return res 50 | 51 | def helper(self, node, res): 52 | if not node: 53 | return 54 | if node.next: 55 | self.helper(node.next, res) 56 | res.append(node.val) 57 | 58 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题07. 重建二叉树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 3 | 输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 4 | 5 | 例如,给出 6 | 7 | 前序遍历 preorder = [3,9,20,15,7] 8 | 中序遍历 inorder = [9,3,15,20,7] 9 | 返回如下的二叉树: 10 | ``` 11 | 3 12 | / \ 13 | 9 20 14 | / \ 15 | 15 7 16 | ``` 17 | 18 | ### 解析1: 19 | 递归构建树。 20 | 21 | * **算法流程:** 22 | * 1. 找到根节点,前序遍历的第一个元素 23 | * 2. 找到根节点在中序遍历的位置(index),根节点的前面的为左子树,右边的为右子树 24 | * 3. 找到前序遍历和中序遍历中的左右子树 25 | * 4. 递归生成左子树,右子树 26 | 27 | * **复杂度:** 28 | | |复杂度|大小|百分比| 29 | |--|--|--|--| 30 | |时间|$O(n)$|184 ms|87.31%| 31 | |空间|$O(1)$|86.9 MB|100%| 32 | 33 | ```python 34 | class Solution(object): 35 | def buildTree(self, preorder, inorder): 36 | if not preorder or not inorder:return None 37 | val = preorder[0] 38 | root = TreeNode(val) 39 | index = inorder.index(val) 40 | root.left = self.buildTree(preorder[1:1+index], inorder[:index]) 41 | root.right = self.buildTree(preorder[1+index:], inorder[index+1:]) 42 | return root 43 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题10- I. 斐波那契数列.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下: 3 | 4 | F(0) = 0,   F(1) = 1 5 | F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 6 | 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 7 | 8 | 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 9 | 10 | ### 解析1: 11 | 本质是DP,对DP进行了拆解。 12 | 13 | * **复杂度:** 14 | | |复杂度|大小|百分比| 15 | |--|--|--|--| 16 | |时间|$O(n)$|32 ms|68.57%| 17 | |空间|$O(1)$|12.8 MB|100%| 18 | 19 | ```python 20 | class Solution: 21 | def fib(self, n: int) -> int: 22 | if n == 0:return 0 23 | f1,f2 = 0,1 24 | for i in range(n-1): 25 | f2,f1 = f1+f2,f2 26 | return f2 % 1000000007 27 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题10- II. 青蛙跳台阶问题.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 3 | 4 | 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 5 | ``` 6 | 示例 1: 7 | 8 | 输入:n = 2 9 | 输出:2 10 | 示例 2: 11 | 12 | 输入:n = 7 13 | 输出:21 14 | ``` 15 | 16 | ### 解析1: 17 | 本质是DP,对DP进行了拆解。注意迭代的次数。和面试题10-I类似。 18 | 19 | * **复杂度:** 20 | | |复杂度|大小|百分比| 21 | |--|--|--|--| 22 | |时间|$O(n)$|24 ms|96.46%| 23 | |空间|$O(1)$|13.1 MB|100%| 24 | 25 | ```python 26 | class Solution: 27 | def numWays(self, n: int) -> int: 28 | if n <= 1:return 1 29 | f1,f2 = 1,2 30 | for i in range(n-2): 31 | f2,f1 = f1+f2,f2 32 | return f2%1000000007 33 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题11. 旋转数组的最小数字.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。   3 | ``` 4 | 示例 1: 5 | 6 | 输入:[3,4,5,1,2] 7 | 输出:1 8 | 示例 2: 9 | 10 | 输入:[2,2,2,0,1] 11 | 输出:0 12 | ``` 13 | 14 | ### 解析1: 15 | 直接返回 16 | 17 | * **复杂度:** 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(n)$|40 ms|100%| 21 | |空间|$O(1)$|13.4 MB|100%| 22 | 23 | ```python 24 | class Solution: 25 | def minArray(self, numbers: List[int]) -> int: 26 | return min(numbers) 27 | ``` 28 | 29 | ### 解析2: 30 | 二分法。 31 | 32 | * **复杂度:** 33 | | |复杂度|大小|百分比| 34 | |--|--|--|--| 35 | |时间|$O(logn)$|92 ms|11.49%| 36 | |空间|$O(1)$|13.4 MB|100%| 37 | 38 | 39 | ```python 40 | class Solution: 41 | def minArray(self, numbers: List[int]) -> int: 42 | if not numbers:return -1 43 | n = len(numbers) 44 | if n == 1:return numbers[0] 45 | 46 | left,right = 0,n-1 47 | while left < right: 48 | mid = (left + right)//2 49 | if numbers[mid] > numbers[right]: 50 | left = mid + 1 51 | elif numbers[mid] < numbers[right]: 52 | right = mid 53 | else: 54 | right -= 1 55 | return numbers[left] 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /剑指offer/面试题12. 矩阵中的路径.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。 3 | 4 | [["a","b","c","e"], 5 | ["s","f","c","s"], 6 | ["a","d","e","e"]] 7 | 8 | 但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。 9 | 10 |   11 | ``` 12 | 示例 1: 13 | 14 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" 15 | 输出:true 16 | ``` 17 | 18 | ### 解析1: 19 | 回溯法,选择每一个点作为起点,然后分别进行回溯。 20 | 21 | * **算法流程:**: 22 | 1. 遍历board中的每一个点,以每个点作为起点; 23 | 2. 设置回溯函数,判断是否存在单词路径,函数参数:坐标点,i,j,已经相同的点数和遍历过的点; 24 | 3. 如果当前元素等于word,并且回溯函数返回True,才确定存在路径; 25 | 4. 若能遍历完所有的点,则返回True,向四周遍历,判断是否存在下一个有效点; 26 | 5. 如果i,j没有越界,并且这个点没有遍历过,且和word[k]相同,则对下一个点进行递归(回溯); 27 | 6. 如果当前路径不存在有效路径,要在visited中清除当前点; 28 | 29 | * **复杂度:** 30 | | |复杂度|大小|百分比| 31 | |--|--|--|--| 32 | |时间|$O(mnk)$|308 ms|86.39%| 33 | |空间|$O(mn)$|15.1 MB|11.65%| 34 | 关于回溯法的复杂度比较麻烦,此处不是很清楚。 35 | 36 | ```python 37 | class Solution: 38 | def exist(self, board: List[List[str]], word: str) -> bool: 39 | n,m = len(board),len(board[0]) 40 | def dfs(i,j,k,visited): 41 | if k == len(word):return True 42 | for dx,dy in [(0,1),(0,-1),(1,0),(-1,0)]: 43 | nx,ny = i+dx,j+dy 44 | if 0<=nx int: 34 | def sum_(x,y): 35 | # 辅助函数,对x,y进行位数相加 36 | res = 0 37 | while x: 38 | res += x%10 39 | x //=10 40 | while y: 41 | res += y%10 42 | y //= 10 43 | return res 44 | 45 | res = set() 46 | if m*n == 0:return 0 47 | queue = [(0,0)] 48 | while queue: 49 | i,j = queue.pop(0) # bfs用队列保存结点,然后先进入的先出队 50 | if (i,j) not in res and sum_(i,j)<=k: 51 | res.add((i,j)) 52 | for dx,dy in [(0,1),(1,0)]: 53 | nx,ny = i+dx,j+dy 54 | if 0<=nx1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。 3 | ``` 4 | 示例 1: 5 | 6 | 输入: 2 7 | 输出: 1 8 | 解释: 2 = 1 + 1, 1 × 1 = 1 9 | 示例 2: 10 | 11 | 输入: 10 12 | 输出: 36 13 | 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36 14 | ``` 15 | 16 | ### 解析1: 17 | 第一种直接可以想到的方法,递归。设F(n)为长度为n的绳子最大乘积。那么可以对其进行拆分, 18 | 递归函数是$F(n) = max(i*F(n-i),i*(n-i)),i=1..n-2$ 19 | * 对F(n)进行拆分,可以拆出来1..n-2的长度,对应乘积大小为$i*F(n-i)$; 20 | * 同时也可以不对其进行拆分,对应乘积大小$i*(n-i)$; 21 | 22 | * **复杂度:** 23 | | |复杂度|大小|百分比| 24 | |--|--|--|--| 25 | |时间|$O(n^2)$|超时|| 26 | |空间|||| 27 | 28 | ```python 29 | class Solution: 30 | def cuttingRope(self, n: int) -> int: 31 | if n <=2:return 1 32 | res = 1 33 | for i in range(1,n-1): 34 | res = max(res, max(i*(n-i),i*self.cuttingRope(n-i))) 35 | return res 36 | ``` 37 | 38 | ### 解析2: 39 | 40 | ### 解析3: 41 | 贪心法:将绳子划分成更多的段,并且没有1,乘积会最大 42 | * 尽可能找到更多的3; 43 | * 没有3了找到尽可能多的2; 44 | * 然后得到1 45 | 46 | * **复杂度:** 47 | | |复杂度|大小|百分比| 48 | |--|--|--|--| 49 | |时间|$O(1)$|40 ms|48.91%| 50 | |空间|$O(1)$|13.4 MB|100%| 51 | 52 | ```python 53 | class Solution: 54 | def cuttingRope(self, n: int) -> int: 55 | if n<=3:return n-1 56 | a,b = n%3,n//3 # 余数 57 | if a == 0:return 3**(b) 58 | elif a == 1:return 3**(b-1)*4 59 | else:return 3**(b)*2 60 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题14- II. 剪绳子 II.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。 3 | 4 | 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 5 | 6 | ### 解析1: 7 | 和上一题一样,没啥好解释的。直接用规律法。 8 | 贪心法:将绳子划分成更多的段,并且没有1,乘积会最大 9 | * 尽可能找到更多的3; 10 | * 没有3了找到尽可能多的2; 11 | * 然后得到1 12 | 13 | * **复杂度:** 14 | | |复杂度|大小|百分比| 15 | |--|--|--|--| 16 | |时间|$O(1)$|40 ms|48.91%| 17 | |空间|$O(1)$|13.4 MB|100%| 18 | ```python 19 | class Solution: 20 | def cuttingRope(self, n: int) -> int: 21 | res = 0 22 | if n<=3:return n-1 23 | a,b = n%3,n//3 # 余数 24 | 25 | if a == 0:res = 3**(b) 26 | elif a == 1:res = 3**(b-1)*4 27 | else:res = 3**(b)*2 28 | return res%(1000000007) 29 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题15. 二进制中1的个数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入:00000000000000000000000000001011 8 | 输出:3 9 | 解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 10 | ``` 11 | 12 | ### 解析1: 13 | 转变成二进制然后直接返回,利用了python的内部机制,这个操作太骚了。 14 | 15 | | |复杂度|大小|百分比| 16 | |--|--|--|--| 17 | |时间|$O(n)$|24 ms|65.73%| 18 | |空间|$O(n)$|11.7 MB|36.16%| 19 | 20 | ```python 21 | class Solution(object): 22 | def hammingWeight(self, n): 23 | return bin(n).count('1') 24 | ``` 25 | 26 | ### 解析2: 27 | * **算法流程:** 28 | 通过位运算来判断有多少个1,明显更费时。将x和1与,如果不是1,则n的二进制的最右边一位不是0,然后向右移位依次判断最右边是否是1即可。复杂度还是$O(n)$但是每次需要判断是否是1,还是很费时的。 29 | 30 | * **复杂度:** 31 | | |复杂度|大小|百分比| 32 | |--|--|--|--| 33 | |时间|$O(n)$|36 ms|11.38%| 34 | |空间|$O(n)$|11.7 MB|36.16%| 35 | 36 | ```python 37 | class Solution(object): 38 | def hammingWeight(self, n): 39 | count = 0 40 | while n: 41 | count += n&1 42 | n >>= 1 43 | return count 44 | ``` 45 | 46 | -------------------------------------------------------------------------------- /剑指offer/面试题16. 数值的整数次方.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 实现 pow(x, n) ,即计算 x 的 n 次幂函数。 3 | 4 | 示例 1: 5 | 6 | 输入: 2.00000, 10 7 | 输出: 1024.00000 8 | 9 | ### 解析1: 10 | 调用函数实现基本操作。 11 | 12 | ```python 13 | class Solution: 14 | def myPow(self, x: float, n: int) -> float: 15 | return x**n 16 | ``` 17 | 18 | ### 解析2: 19 | 分治法,不断对其进行二分。当然肯定不如python自己实现的更快。主要注意边界条件,递归时,参数的迭代更新。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(logn)$|48 ms|71.81%| 24 | |空间|$O(1)$|13.8 MB|5.24%| 25 | 26 | ```python 27 | class Solution: 28 | def myPow(self, x: float, n: int) -> float: 29 | 30 | def helper(x,n): 31 | if n == 0:return 1 32 | if n%2 == 0:return helper(x*x,n//2) 33 | return helper(x*x, (n-1)//2)*x 34 | 35 | if n >= 0:return helper(x,n) 36 | return 1/helper(x,-n) 37 | ``` 38 | -------------------------------------------------------------------------------- /剑指offer/面试题17. 打印从1到最大的n位数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。 3 | 4 | 示例 1: 5 | 6 | 输入: n = 1 7 | 输出: [1,2,3,4,5,6,7,8,9] 8 |   9 | 10 | 说明: 11 | 12 | 用返回一个整数列表来代替打印 13 | n 为正整数 14 | 15 | ### 解析1: 16 | 没什么好说的,不写了。 17 | 18 | ```python 19 | class Solution: 20 | def printNumbers(self, n: int) -> List[int]: 21 | return [i for i in range(1,10**n)] 22 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题18. 删除链表的节点.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 3 | 4 | 返回删除后的链表的头节点。 5 | 6 | 注意:此题对比原题有改动 7 | ``` 8 | 示例 1: 9 | 10 | 输入: head = [4,5,1,9], val = 5 11 | 输出: [4,1,9] 12 | 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. 13 | 示例 2: 14 | 15 | 输入: head = [4,5,1,9], val = 1 16 | 输出: [4,5,9] 17 | 解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9. 18 | ``` 19 | 20 | ### 解析1: 21 | * **算法流程:** 22 | * 1. 设置哑结点,便于处理(关于哑结点的设置不是很明确和熟练) 23 | * 2. 如果第一个结点就是被删除结点,直接返回head.next 24 | * 3. 遍历链表,如果出现被删除结点,则进行替换,head.next = head.next.next 25 | * 4. 循环要保证head和head.next同时存在。 26 | 27 | * **复杂度:** 28 | | |复杂度|大小|百分比| 29 | |--|--|--|--| 30 | |时间|$O(n)$|144 ms|7.39%| 31 | |空间|$O(1)$|29.6 MB|100%| 32 | 33 | ```python 34 | class Solution: 35 | def deleteNode(self, head: ListNode, val: int) -> ListNode: 36 | dummy = ListNode(0) 37 | dummy.next = head 38 | if head.val == val:return head.next 39 | while head and head.next: 40 | if head.next.val == val: 41 | head.next = head.next.next 42 | head = head.next 43 | return dummy.next 44 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题21. 调整数组顺序使奇数位于偶数前面.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 3 | 4 | ``` 5 | 示例: 6 | 7 | 输入:nums = [1,2,3,4] 8 | 输出:[1,3,2,4] 9 | 注:[3,1,2,4] 也是正确的答案之一。 10 | ``` 11 | 12 | ### 解析1: 13 | * **算法流程:** 14 | * 遍历数组,奇偶分别放在不同的list,然后返回 15 | 16 | * **复杂度:** 17 | | |复杂度|大小|百分比| 18 | |--|--|--|--| 19 | |时间|$O(n)$|144 ms|7.39%| 20 | |空间|$O(1)$|29.6 MB|100%| 21 | 22 | ```python 23 | class Solution: 24 | def exchange(self, nums: List[int]) -> List[int]: 25 | odd = [] 26 | ou = [] 27 | for num in nums: 28 | if num % 2 == 0: 29 | ou.append(num) 30 | else:odd.append(num) 31 | return odd+ou 32 | ``` 33 | 34 | ### 解析2: 35 | Python一行搞定,需要使用到sorted的key。 36 | 37 | ```python 38 | class Solution: 39 | def exchange(self, nums: List[int]) -> List[int]: 40 | return sorted(nums, key=lambda x:x%2 == 0) 41 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题22. 链表中倒数第k个节点.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。 3 |   4 | ``` 5 | 示例: 6 | 7 | 给定一个链表: 1->2->3->4->5, 和 k = 2. 8 | 9 | 返回链表 4->5. 10 | ``` 11 | 12 | ### 解析1: 13 | * **算法流程:** 14 | * 双指针,第一个指针先走k步; 15 | * 两个指针再同时开始走,直到第一个指针先走到头 16 | 17 | * **复杂度:** 18 | | |复杂度|大小|百分比| 19 | |--|--|--|--| 20 | |时间|$O(n)$|104 ms|6.52%| 21 | |空间|$O(1)$|28.6 MB|100%| 22 | 23 | ```python 24 | class Solution: 25 | def getKthFromEnd(self, head: ListNode, k: int) -> ListNode: 26 | phead1 = head 27 | phead2 = head 28 | for _ in range(k): 29 | if phead1: 30 | phead1 = phead1.next 31 | while phead1: 32 | phead1 = phead1.next 33 | phead2 = phead2.next 34 | return phead2 35 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题24. 反转链表.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。 3 | 4 |   5 | 6 | 示例: 7 | 输入: 1->2->3->4->5->NULL 8 | 输出: 5->4->3->2->1->NULL 9 |   10 | 11 | 限制: 12 | 0 <= 节点个数 <= 5000 13 | 14 | ### 解析1: 15 | 16 | ```python 17 | class Solution: 18 | def reverseList(self, head: ListNode) -> ListNode: 19 | res = None 20 | # while head:res,res.next, head = head, res, head.next # 一行写法 21 | while head: 22 | temp = head.next 23 | head.next = res 24 | res = head 25 | head = temp 26 | return res 27 | ``` 28 | -------------------------------------------------------------------------------- /剑指offer/面试题26. 树的子结构.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) 3 | 4 | B是A的子结构, 即 A中有出现和B相同的结构和节点值。 5 | ``` 6 | 例如: 7 | 给定的树 A: 8 | 9 |      3 10 |     / \ 11 |    4   5 12 |   / \ 13 |  1   2 14 | 给定的树 B: 15 | 16 |    4  17 |   / 18 |  1 19 | 返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。 20 | ``` 21 | 22 | ### 解析1: 23 | 遍历树A的每一个结点,判断是否和B树相同。判断两个树是否相同,通过dfs遍历。判断两棵树是否相等的是否,注意AB的顺序不能颠倒,此处和单纯判断AB两棵树是否相等还有些不同。 24 | 25 | * **算法流程:** 26 | * 1.如果A或者B为空,返回False; 27 | * 2.dfs判断整棵树是否相等; 28 | * 3.判断两棵树是否相等,比较根节点值是否相等,然后递归判断左右子树是否相等; 29 | 30 | 31 | ```python 32 | class Solution: 33 | def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool: 34 | def dfs(A,B): 35 | if not B:return True # 注意先后关系,这两句的顺序是不能颠倒的 36 | if not A:return False # 先判断B是否为空 37 | return A.val==B.val and dfs(A.left,B.left) and dfs(A.right, B.right) 38 | 39 | if not A or not B:return False 40 | return dfs(A,B) or self.isSubStructure(A.left,B) or self.isSubStructure(A.right, B) 41 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题27. 二叉树的镜像.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 请完成一个函数,输入一个二叉树,该函数输出它的镜像。 3 | ``` 4 | 例如输入: 5 | 6 |      4 7 |    /   \ 8 |   2     7 9 |  / \   / \ 10 | 1   3 6   9 11 | 镜像输出: 12 | 13 |      4 14 |    /   \ 15 |   7     2 16 |  / \   / \ 17 | 9   6 3   1 18 | 19 | 示例 1: 20 | 21 | 输入:root = [4,2,7,1,3,6,9] 22 | 输出:[4,7,2,9,6,3,1] 23 | ``` 24 | 25 | ### 解析1: 26 | 27 | ```python 28 | class Solution: 29 | def mirrorTree(self, root: TreeNode) -> TreeNode: 30 | if not root:return root 31 | # 先交换左右子树,然后对左右子树递归 32 | root.left,root.right = root.right, root.left 33 | self.mirrorTree(root.right) 34 | self.mirrorTree(root.left) 35 | return root 36 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题28. 对称的二叉树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 3 | ``` 4 | 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 5 | 6 |     1 7 |    / \ 8 |   2   2 9 |  / \ / \ 10 | 3  4 4  3 11 | 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 12 | 13 |     1 14 |    / \ 15 |   2   2 16 |    \   \ 17 |    3    3 18 | ``` 19 | 20 | 21 | ### 解析1: 22 | **设置dfs辅助函数,比较左子树结点和右子树结点是否是相等的,以及左右子树是否对称。**--再判断左子树的右结点和右子树的左结点是否是相等的,左子树的左结点和右子树的右结点是否是相等的。 23 | 24 | * **算法流程:** 25 | 1. 设置辅助函数,来判断两棵树是否相等。如果两棵树均为空返回True; 26 | 2. 如果两棵树一个为空,一个不为空,则返回False; 27 | 3. 判断两棵树结点值是否相等、左树的左结点和右树的右结点是否相等、左树的右结点和右树的左结点是否相等。 28 | 29 | * **复杂度:** 30 | | |复杂度|大小|百分比| 31 | |--|--|--|--| 32 | |时间|$O(n)$|40 ms|12.15%| 33 | |空间|$O(n)$|12.1 MB|10.14%| 34 | 35 | ```python 36 | class Solution(object): 37 | def isSymmetric(self, root): 38 | def dfs(left,right): 39 | if not left and not right:return True 40 | if not left or not right:return False 41 | 42 | return left.val == right.val and \ 43 | dfs(left.left, right.right) and \ 44 | dfs(left.right, right.left) 45 | return dfs(root, root) 46 | ``` 47 | -------------------------------------------------------------------------------- /剑指offer/面试题30. 包含min函数的栈.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。 3 | 4 |   5 | 6 | 示例: 7 | 8 | MinStack minStack = new MinStack(); 9 | minStack.push(-2); 10 | minStack.push(0); 11 | minStack.push(-3); 12 | minStack.min(); --> 返回 -3. 13 | minStack.pop(); 14 | minStack.top(); --> 返回 0. 15 | minStack.min(); --> 返回 -2. 16 | 17 | ### 解析1: 18 | 建立一个辅助栈,每次将已经压入栈内的最小值压入辅助栈。 19 | 20 | * **算法流程:** 21 | 1. 主要是push的时候,如果辅助栈为空或者新元素小于辅助栈顶元素的时候push新元素,否则push辅助栈顶元素; 22 | 2. pop两个栈均pop,注意栈为空的情况 23 | 24 | * **复杂度:** 25 | | |复杂度|大小|百分比| 26 | |--|--|--|--| 27 | |时间|$O(1)$|116 ms|31.87%| 28 | |空间|$O(1)$|16.9 MB|100%| 29 | 30 | ```python 31 | class MinStack: 32 | def __init__(self): 33 | """ 34 | initialize your data structure here. 35 | """ 36 | self.stack1 = [] 37 | self.stack2 = [] 38 | 39 | def push(self, x: int) -> None: 40 | self.stack1.append(x) 41 | if not self.stack2 or x < self.stack2[-1]: 42 | self.stack2.append(x) 43 | else: 44 | self.stack2.append(self.stack2[-1]) 45 | 46 | def pop(self) -> None: 47 | if not self.stack1:return None 48 | self.stack1.pop() 49 | self.stack2.pop() 50 | 51 | def top(self) -> int: 52 | return self.stack1[-1] 53 | 54 | def min(self) -> int: 55 | return self.stack2[-1] 56 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题32 - I. 从上到下打印二叉树.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。 3 | 4 | ``` 5 | 例如: 6 | 给定二叉树: [3,9,20,null,null,15,7], 7 | 8 | 3 9 | / \ 10 | 9 20 11 | / \ 12 | 15 7 13 | 返回: 14 | 15 | [3,9,20,15,7] 16 | ``` 17 | 18 | ### 解析1: 19 | 和LeetCode [102.二叉树的层次遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/),有所不同,无需将每层的打印到一个数组里面,用一个二维数组来表示。 20 | 21 | ```python 22 | class Solution: 23 | def levelOrder(self, root: TreeNode) -> List[int]: 24 | if not root:return [] 25 | queue = [root] 26 | res = [] 27 | while queue: 28 | for _ in range(len(queue)): 29 | # 遍历当前层的所有结点 30 | # 依次进行pop,使用队列,先入先出,先入左结点 31 | temp = queue.pop(0) 32 | res.append(temp.val) 33 | if temp.left:queue.append(temp.left) 34 | if temp.right:queue.append(temp.right) 35 | return res 36 | ``` 37 | 稍微简单的写法,无需先遍历一层的结点。 38 | ```python 39 | class Solution: 40 | def levelOrder(self, root: TreeNode) -> List[int]: 41 | if not root:return [] 42 | queue = [root] 43 | res = [] 44 | while queue: 45 | temp = queue.pop(0) 46 | res.append(temp.val) 47 | if temp.left:queue.append(temp.left) 48 | if temp.right:queue.append(temp.right) 49 | return res 50 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题32 - III. 从上到下打印二叉树 III.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 3 | ``` 4 | 例如: 5 | 给定二叉树 [3,9,20,null,null,15,7], 6 | 7 | 3 8 | / \ 9 | 9 20 10 | / \ 11 | 15 7 12 | 返回锯齿形层次遍历如下: 13 | 14 | [ 15 | [3], 16 | [20,9], 17 | [15,7] 18 | ] 19 | ``` 20 | 21 | ### 解析1: 22 | 和上一题层次遍历差不多,只是加一个层的判断,奇数和偶数层的处理不同,增加一个判断即可。 23 | 24 | * **复杂度:** 25 | | |复杂度|大小|百分比| 26 | |--|--|--|--| 27 | |时间|$O(n)$|40 ms|97.71%| 28 | |空间|$O(n)$|13.8 MB|5.96%| 29 | 30 | ```python 31 | class Solution: 32 | def levelOrder(self, root: TreeNode) -> List[List[int]]: 33 | if not root:return [] 34 | queue = [root] 35 | level = 0 36 | res = [] 37 | while queue: 38 | temp = [] 39 | for _ in range(len(queue)): 40 | node = queue.pop(0) 41 | temp.append(node.val) 42 | if node.left:queue.append(node.left) 43 | if node.right:queue.append(node.right) 44 | if level%2 == 0: 45 | res.append(temp) 46 | else: 47 | res.append(temp[::-1]) 48 | level += 1 49 | return res 50 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题39. 数组中出现次数超过一半的数字.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 3 | 4 |   5 | 6 | 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 7 | 8 |   9 | ``` 10 | 示例 1: 11 | 12 | 输入: [1, 2, 3, 2, 2, 2, 5, 4, 2] 13 | 输出: 2 14 | ``` 15 | 16 | ### 解析1: 17 | 18 | * **复杂度:** 19 | | |复杂度|大小|百分比| 20 | |--|--|--|--| 21 | |时间|$O(n)$|64 ms|84%| 22 | |空间|$O(n)$|15 MB|100%| 23 | 24 | ```python 25 | class Solution: 26 | def majorityElement(self, nums: List[int]) -> int: 27 | return sorted(nums)[len(nums)//2] 28 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题47. 礼物的最大价值.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物? 3 | 4 |   5 | ``` 6 | 示例 1: 7 | 8 | 输入: 9 | [ 10 |   [1,3,1], 11 |   [1,5,1], 12 |   [4,2,1] 13 | ] 14 | 输出: 12 15 | 解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物 16 | ``` 17 | 18 | ### 解析1: 19 | 典型的DP问题。 20 | **算法流程:** 21 | 状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j] 22 | 根据状态状态转移方程,首先填充第一行,第一列 23 | 24 | **复杂度:** 25 | | |复杂度|大小|百分比| 26 | |--|--|--|--| 27 | |时间|$O(n)$|52 ms|81.39%| 28 | |空间|$O(1)$|12.6 MB|23.34%| 29 | 30 | ```python 31 | class Solution: 32 | def maxValue(self, grid: List[List[int]]) -> int: 33 | m,n = len(grid), len(grid[0]) 34 | dp = [[0 for _ in range(n)] for _ in range(m)] 35 | 36 | dp[0][0] = grid[0][0] 37 | for i in range(1, n): 38 | dp[0][i] = dp[0][i-1] + grid[0][i] 39 | 40 | for i in range(1, m): 41 | dp[i][0] = dp[i-1][0] + grid[i][0] 42 | 43 | for i in range(1,m): 44 | for j in range(1,n): 45 | dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j] 46 | 47 | return dp[m-1][n-1] 48 | 49 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题50. 第一个只出现一次的字符.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 3 | ``` 4 | 示例: 5 | 6 | s = "abaccdeff" 7 | 返回 "b" 8 | 9 | s = "" 10 | 返回 " " 11 | ``` 12 | 13 | ### 解析1: 14 | 求次数,遍历即可。 15 | 16 | ```python 17 | class Solution: 18 | def firstUniqChar(self, s: str) -> str: 19 | cnt = dict() 20 | for c in s: 21 | cnt[c] = cnt.get(c,0)+1 22 | 23 | for c in s: 24 | if cnt[c] == 1: 25 | return c 26 | return ' ' 27 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题55 - I. 二叉树的深度.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。 3 | ``` 4 | 例如: 5 | 6 | 给定二叉树 [3,9,20,null,null,15,7], 7 | 8 | 3 9 | / \ 10 | 9 20 11 | / \ 12 | 15 7 13 | 返回它的最大深度 3 14 | ``` 15 | 16 | ### 解析1: 17 | 递归求左右子树的深度,然后返回较大值+1; 18 | 19 | ```python 20 | class Solution: 21 | def maxDepth(self, root: TreeNode) -> int: 22 | if not root:return 0 23 | return max(self.maxDepth(root.left),self.maxDepth(root.right))+1 24 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题57 - II. 和为s的连续正数序列.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。 3 | 4 | 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。 5 | 6 | 示例 1: 7 | 8 | 输入:target = 9 9 | 输出:[[2,3,4],[4,5]] 10 | 示例 2: 11 | 12 | 输入:target = 15 13 | 输出:[[1,2,3,4,5],[4,5,6],[7,8]] 14 | 15 | ### 解析1: 16 | 双指针法,注意阈值停止的条件,和备选空间,备选空间为1到target/2的上取整。 17 | 18 | * **算法流程:** 19 | * 1.生成备选数组,1到target/2的上取整 20 | * 2.双指针,遍历nums两个指针间的区间,根据求和大小,分别更新指针; 21 | * 如果出现相等,两个指针都右移; 22 | 23 | 24 | ```python 25 | class Solution: 26 | def findContinuousSequence(self, target: int) -> List[List[int]]: 27 | mid = target // 2+1 28 | nums = [i for i in range(1,mid+1)] 29 | i,j = 0,1 30 | res = [] 31 | while i target:i+=1 34 | elif total List[int]: 6 | n = len(nums) 7 | if n<2:return [] 8 | nums_dict = dict() 9 | for i in range(n): 10 | if nums[i] not in nums_dict: 11 | nums_dict[nums[i]] = i 12 | if target-nums[i] in nums_dict and i != nums_dict[target-nums[i]]: 13 | return [nums[i],target-nums[i]] 14 | 15 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题58 - I. 翻转单词顺序.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个字符串,逐个翻转字符串中的每个单词。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: "the sky is blue" 8 | 输出: "blue is sky the" 9 | 示例 2: 10 | 11 | 输入: "  hello world!  " 12 | 输出: "world! hello" 13 | 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 14 | ``` 15 | 16 | ### 解析1: 17 | 拆分,倒序,拼接,全都是python的split的强大。 18 | 19 | ```python 20 | class Solution: 21 | def reverseWords(self, s: str) -> str: 22 | return ' '.join(s.split()[::-1]) 23 | ``` 24 | 25 | ### 解析2: 26 | 遍历添加法,去掉两边的空格,然后从尾部开始倒着添加。 27 | 28 | ```python 29 | class Solution: 30 | def reverseWords(self, s: str) -> str: 31 | s = s.strip() 32 | res = "" 33 | i, j = len(s) - 1, len(s) 34 | while i > 0: 35 | if s[i] == ' ': 36 | res += s[i + 1: j] + ' ' 37 | while s[i] == ' ': i -= 1 38 | j = i + 1 39 | i -= 1 40 | return res + s[:j] 41 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题58 - II. 左旋转字符串.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 3 | 4 | ``` 5 | 示例 1: 6 | 7 | 输入: s = "abcdefg", k = 2 8 | 输出: "cdefgab" 9 | 示例 2: 10 | 11 | 输入: s = "lrloseumgh", k = 6 12 | 输出: "umghlrlose" 13 | ``` 14 | 15 | ### 解析1: 16 | 拼接旋转,没啥好说的。 17 | 18 | ```python 19 | class Solution: 20 | def reverseLeftWords(self, s: str, n: int) -> str: 21 | return s[n:]+s[:n] 22 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题64. 求1+2+…+n.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linxid/Leetcode_Python/bfbb5d4ded43e104f621f02e7559ddc721876e09/剑指offer/面试题64. 求1+2+…+n.md -------------------------------------------------------------------------------- /剑指offer/面试题65. 不用加减乘除做加法.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。 3 | 4 |   5 | 6 | 示例: 7 | 8 | 输入: a = 1, b = 1 9 | 输出: 2 10 | 11 | 12 | 提示: 13 | 14 | a, b 均可能是负数或 0 15 | 结果不会溢出 32 位整数 16 | 17 | ### 解析1: 18 | 直接求解得了。 19 | ```python 20 | class Solution: 21 | def add(self, a: int, b: int) -> int: 22 | return a+b 23 | ``` -------------------------------------------------------------------------------- /剑指offer/面试题68 - II. 二叉树的最近公共祖先.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 3 | 4 | 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 5 | 6 | ``` 7 | 例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4] 8 | 9 | 3 10 | / \ 11 | 5 1 12 | / \ / \ 13 | 6 2 0 8 14 | / \ 15 | 7 4 16 | ``` 17 | 18 | ### 解析1: 19 | 递归,依次判断结点值是否等于p或者q,维护两个变量left和right,判断p,q是否出现在左右子树中。 20 | 21 | | |复杂度|大小|百分比| 22 | |--|--|--|--| 23 | |时间|$O(n)$|76 ms|99.94%| 24 | |空间|$O(1)$|28.7 MB|5.04%| 25 | 26 | 步骤: 27 | 1. 设置一个辅助函数,求以node为根节点的子树中,是否出现了p或者q; 28 | 2. 如果左子树,右子树和node超过两个为True,也就是左或右子树包含p或q结点,他便是最低公共祖先; 29 | 30 | ```python 31 | class Solution: 32 | def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': 33 | self.res = None 34 | def helper(node): 35 | if not node:return False 36 | left = helper(node.left) 37 | right = helper(node.right) 38 | 39 | mid = node == p or node == q 40 | if mid + left + right >= 2: 41 | self.res = node 42 | return mid or left or right 43 | helper(root) 44 | return self.res 45 | ``` -------------------------------------------------------------------------------- /面试题49. 丑数.md: -------------------------------------------------------------------------------- 1 | ### 题目: 2 | 编写一个程序,找出第 n 个丑数。 3 | 4 | 丑数就是只包含质因数 2, 3, 5 的正整数。 5 | 6 | 示例: 7 | 8 | 输入: n = 10 9 | 输出: 12 10 | 解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。 11 | 说明:   12 | 13 | 1 是丑数。 14 | n 不超过1690。 15 | 16 | ### 解析1: 17 | 动态规划。 18 | 19 | **算法流程:** 20 | 重点是DP的状态转移方程:`dp[i] = min(2*dp[p2], 3*dp[p3], 5*dp[p5])` 21 | p2,p3,p5分别是三个指针,代表最靠前,最新的没有被乘以2,3,5的数值的指针。这里有点拗口。我们一次求丑数的时候,新的丑数肯定是以前的某个数或者某几个数乘以2,3,5得到的。如果最后一个数,大于前面的数乘以2,3,5,那么将对应的指针加1。因为n很小,所以复杂度可以认为是O(1)。 22 | 23 | 三个指针代表什么呢,代表,当前指针后面的数都不是当前值乘以2/3/5得到的。比如p2,代表已知p2后面的数,都不是p2值乘以2得到的。 24 | 25 | **复杂度:** 26 | | |复杂度|大小|百分比| 27 | |--|--|--|--| 28 | |时间|$O(1)$|240 ms|43.20%| 29 | |空间|$O(n)$|13.7 MB|6.09%| 30 | 31 | ```python 32 | class Solution: 33 | def nthUglyNumber(self, n: int) -> int: 34 | if n == 1:return 1 35 | res = [0]*n 36 | p2 = p3 = p5 = 0 37 | res[0] = 1 38 | for i in range(1, n): 39 | res[i] = min(2*res[p2], 3*res[p3], 5*res[p5]) 40 | if res[i] >= 2 * res[p2]: 41 | p2 += 1 42 | if res[i] >= 3 * res[p3]: 43 | p3 += 1 44 | if res[i] >= 5 * res[p5]: 45 | p5 += 1 46 | return res[-1] 47 | ``` --------------------------------------------------------------------------------