2. 实现Singleton模式
使用__new__
控制实例创建过程
1 | class Singleton: |
使用decorator
1 | from functools import wraps |
使用元类
1 | class Singleton(type): |
3 数组中重复的数字
牛客网传送门
书中参数还传了一个数组用来保存重复的数字,身为一个Pythoner,直接返回tuple。
1 | def duplicate(nums: list) -> int: |
3_1 数组中重复的数字,不能修改数组
AcWing传送门
元素范围变成了1~n。此方法有缺陷,不能找出所有的重复的数字,因为在1~2的范围里有1和2两个数字,这个范围的数字也出现两次,不能确定是每个数字各出现一次还是某个数字出现了两次。
1 | def find_duplicate(nums: list) -> int: |
4. 二维数组中的查找
牛客网传送门
AcWing传送门
LeetCode传送门
选取右上角为起始点。
1 | def find(target, array): |
更为优雅的方法。
1 | def searchMatrix(self, matrix, target): |
5.替换空格
牛客网传送门
AcWing传送门
1 | def replaceSpace(self, s): |
6.从尾到头打印链表
AcWing传送门
1 | def printListFromTailToHead(self, listNode): |
7.重建二叉树
LeetCode传送门
说明:根据前序遍历和中序遍历重建二叉树,假设遍历结果中不包含重复的数字。1
2
3
4
5
6
7
8
9def buildTree(preorder, inorder):
if preorder == []:
return None
root_val = preorder[0]
root = TreeNode(root_val)
cut = inorder.index(root_val)
root.left = buildTree(preorder[1:cut+1], inorder[:cut])
root.right = buildTree(preorder[cut+1:], inorder[cut+1:])
return root
方法二:空间复杂度更低的解法。
1 | def buildTree(self, preorder: 'List[int]', inorder: 'List[int]') -> 'TreeNode': |
8 二叉树的下一个节点
牛客网传送门
AcWing传送门
如果节点有右子树,那么下一个节点为右子树中的最左节点;
如果节点是父节点的左子节点,那么父节点就是下一个节点;
如果节点是父节点的右子节点,沿着父节点向上遍历找到祖先节点中,为父节点左子节点的点,这个左子节点的父节点就是下一个节点。
1 | def GetNext(self, pNode): |
9.用两个栈实现队列
LeetCode传送门
1 | class MyQueue: |
9.1用两个队列实现栈
LeetCode传送门
两个队列1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class MyStack:
def __init__(self):
from collections import deque
self.q1, self.q2 = deque(), deque()
def push(self, x: int) -> None:
self.q2.append(x)
while self.q1:
self.q2.append(self.q1.popleft())
self.q1, self.q2 = self.q2, self.q1
def pop(self) -> int:
return self.q1.popleft()
def top(self) -> int:
return self.q1[0]
def empty(self) -> bool:
return not self.q1
单队列旋转1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class MyStack:
def __init__(self):
from collections import deque
self.q = deque()
def push(self, x: int) -> None:
self.q.append(x)
# self.q.rotate(1) 这里是用了双端队列的特性
# self.q.rotate(1-len(self.q)) 这里和下面循环是一样的效果
for _ in range(len(self.q)-1):
self.q.append(self.q.popleft())
def pop(self) -> int:
return self.q.popleft()
def top(self) -> int:
return self.q[0]
def empty(self) -> bool:
return not self.q
10.斐波那契数列
LeetCode传送门
1 | def fibonacci(n): |
11.旋转数组的最小数字
LeetCode传送门
思路:通过二分法不断缩小范围,由于mid是整除,最后l==mid,并且nums[mid] > nums[r]的。1
2
3
4
5
6
7
8
9
10
11
12def find_min(nums):
l, r = 0, len(nums)-1
if nums[l] < nums[r]:
return nums[l]
while l <= r:
mid = (l+r) // 2
if nums[mid] > nums[l]:
l = mid
elif nums[mid] < nums[r]:
r = mid
else:
return nums[r]
12.矩阵中的路径
LeetCode传送门
1 | def exist(self, board: List[List[str]], word: str) -> bool: |
13. 机器人的运动范围
AcWing传送门
1 | def movingCount(self, m: int, n: int, k: int) -> int: |
14.剪绳子
AcWing传送门
说明:数学思想,当n>=5
时,2(n-2)>n
并且3(n-3)>n
,而且3(n-3) >= 2(n-2)
,所以尽可能多剪长度为3的绳子。如果长度为4的时候,2*2>3*1
,所以4的时候就剪成2*2
的两段。
1 | def cut_rope(length): |
15.二进制中1的个数
LeetCode传送门
方法一:常规解法,使用1与n作与运算,如果不是0说明,含有一个1。1
2
3
4
5
6def hamming_weight(n):
count = 0
for _ in range(32):
count += (n&1==1)
n >>= 1
return count
方法二:关键点是,一个数n和n-1的与运算操作,相当于去掉了最右面的1。1
2
3
4
5
6def hamming_weigth(n):
bits = 0
while n:
bits += 1
n = (n-1) & n
return bits
16.数值的整数次方
LeetCode传送门
这里需要注意的一点ans *= ans
不能写成ans = ans**2
因为浮点数是有范围的,可能会超出范围报错。
1 | def myPow(self, x: float, n: int) -> float: |
17 打印从1到最大的n位数
404.
打印呗,反正Python的int没有长度限制。
1 | def print_n(n: int): |
18.删除链表中的节点
LeetCode传送门
开始看到这题的思路是,要是能拿到父节点就好了,然后这道题需要别的思路,其关键在于复制1
2
3def deleteNode(self, node):
node.val = node.next.val # 4->1->1->9
node.next = node.next.next # 4->1->9
19.正则表达式
LeetCode传送门
先考虑没有*
的情况,通过一个递归逐个字符判断
1 | def match(text, pattern): |
当*
出现时,一定会在前面跟一个其他字符,所以一定会出现在pattern[1]的位置。一种情况是我们忽略这对pattern,因为可以出现0次;另一种情况是匹配上这个字符,用递归的方式匹配下一个。
一定要用f_match = bool(s),否则结果可能输出''
1 |
|
20. 表示数值的字符串
65. Valid Number
此处留坑,排名第一的python答案暂时没有理解。
21.调整数组顺序使奇数位于偶数前面
AcWing传送门
生成器写法,
1 | def exchange(self, nums: List[int]) -> List[int]: |
时间:O(n), 空间O(1)
1 | def reOrderArray(self, array): |
看了一下没有通过牛客网的测试用例,因为题目有些不同,牛客网要求奇数和奇数,偶数和偶数之前的相对位置不变。
1 | def reOrderArray(array): |
不使用sort
1 | def reOrderArray(self, array): |
22.链表中倒数第k个节点
AcWing传送门
思路:两个指针,快指针先走k步,然后两个一起走,快指针走到尾节点时,慢指针在倒数第k个节点。
需考虑k=0时和fast已经走到尾节点的情况。
1 | def FindKthToTail(self, head, k): |
23.链表中环的入口节点
LeetCode传送门
- 首先判断此链表是否有环。
- 然后再相交点和头结点一起走,一定会在入口相遇。
1 | def detectCycle(self, head): |
24.反转链表
LeetCode传送门
1 | def reverseList(self, head): |
25.合并两个有序链表
LeetCode传送门
方法1:iteratively 迭代
1 | def mergeTwoLists(l1, l2): |
方法2:recursively 递归
1 | def mergeTwoLists(l1, l2): |
26.树的子结构
572. Subtree of Another Tree二刷的时候突然发现,此题和LeetCode中不同。LeetCode中子树4-1-2
返回False因为2下边还有节点,所以不一样;而书中认为True,不考虑2下边的节点。
1 | 3 |
1 | def isSubStructure(self, s: TreeNode, t: TreeNode) -> bool: |
方法二:学到一个递归写法,更简单。
1 | def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool: |
27.二叉树的镜像
AcWing传送门
1 | def Mirror(self, root): |
迭代
1 | def Mirror(self, root): |
28.对称的二叉树
LeetCode传送门
1 | 1 |
方法一:recursively.
1 | def isSymmetric(self, root: 'TreeNode') -> 'bool': |
方法二:iteratively.
1 | def isSymmetric(self, root: 'TreeNode') -> 'bool': |
29.顺时针打印矩阵
LeetCode传送门
这里注意一点matrix.pop(0)
需要转成list,因为zip函数中的每个元素是一个tuple,如果不转变成了一个tuple+list
,会抛出异常。
1 | def spiralOrder(self, matrix): |
方法二:迭代。
1 | def spiralOrder(self, matrix: List[List[int]]) -> List[int]: |
此题有个变形,如果逆时针该如何打印。这样的话情况稍微复杂一些。
1 | def anti_clock_wise(self, matrix) |
30.包含min函数的栈
LeetCode传送门
1 | class MinStack: |
31.栈的压入、弹出序列
LeetCode传送门
1 | def validateStackSequences(self, pushed: 'List[int]', popped: 'List[int]') -> 'bool': |
32.从上到下打印二叉树
AcWing传送门
1 | def levelOrder(self, root: TreeNode) -> List[int]: |
32.1分层从上到下打印二叉树
LeetCode传送门
1 | def levelOrder(self, root: 'TreeNode') -> 'List[List[int]]': |
32.2之字形打印二叉树
LeetCode传送门
1 | def zigzagLevelOrder(self, root: 'TreeNode') -> 'List[List[int]]': |
33.是否是二叉树的后序遍历
AcWing传送门
1 | def VerifySquenceOfBST(self, seq): |
34.二叉树和为某一值的路径
LeetCode传送门
1 | sum = 22 |
1 | def pathSum(self, root: 'TreeNode', total: 'int') -> 'List[List[int]]': |
recursively. 先找出所有路径,再过滤,实际上和257题一样。不过这并没有把这道题的特性涵盖进去。
1 | def pathSum(self, root, sum_val): |
方法二:模板写法。
1 | def pathSum(self, root: TreeNode, total: int) -> List[List[int]]: |
1 | def pathSum(self, root, sum): |
35.复杂链表的复制
LeetCode传送门
方法一:遍历两次。
1 | def copyRandomList(self, head: 'Node') -> 'Node': |
Time-O(n), Memory-O(n). 这种方式是相当于把第一次迭代的过程委托给了defaultdict
,通过创建一个默认的对象,再去修改它的label值。
1 | def copyRandomList(self, head: 'Node') -> 'Node': |
36.二叉搜索树与双向链表
LeetCode有此题,但是不是免费的。
AcWing传送门
方法一:中序遍历,再构造链表。1 | def convert(self, root): |
1 | def Convert(self, root): |
1 | def Convert(self, root): |
37.序列化二叉树
LeetCode传送门
1 | class Codec: |
38.字符串的排列
46. Permutations IILeetCode传送门
使用itertools
1 | def Permutation(self, ss): |
这里注意几点:
为什么要判断
if not ss
,是因为如果ss=''
的时时候,返回了['']
而不是[]
。因为这里返回了一个空的tuple
,所以在列表推导式中是有一个元素的。1
2>> list(permutations('', 0))
[()]- 为什么使用
set
去重,因为当ss='aa'
的时候,牛客网的测试用例要求返回一个元素,即['aa']
。 - 排序也是为了满足测试用例。
自己实现。这里拆成两个方法的原因还是因为ss=''
的时候会影响递归循环。
1 | def Permutation(self, ss): |
1 | def Permutation(self, ss): |
39.数组中出现次数超过一半的数字
LeetCode传送门
方法一:排序. Time-O(nlogn), Space-O(n)
1 | def majority_element(nums): |
方法二:Counter Time-O(n), Space-O(n)
1 | def majority_element(nums): |
1 | def majorityElement(self, nums): |
40.最小的k个数
相似题目,但是求最大的k个数LeetCode传送门
牛客网传送门
1 | def GetLeastNumbers_Solution(self, tinput, k): |
使用堆,不改变原数组
1 | def GetLeastNumbers_Solution(self, tinput, k): |
41.数据流中的中位数
LeetCode传送门
思路:使用两个堆,最大堆存储较小的数据,最小堆存储较大的数据。添加数字时,先添加到最大堆,然后最大堆返回一个最大的数字给最小堆,最后为了平衡,可能需要最小堆还给最大堆一个最小值,以保证最大堆的长度>=最小堆的长度。由于headpq是最小堆,所以使用取反实现最大堆。添加数字:Time-O(logn),取出中位数:Time-O(1)。
1 | Adding number 41 |
1 | import heapq as hq |
42.连续子数组的最大和
LeetCode传送门
方法一:书中的思想。
1 | def maxSubArray(self, nums): |
方法二:one-liner。注意accumulate
是把函数放到后面的。
1 | def maxSubArray(self, nums): |
43.1~n整数中1出现的次数
LeetCode传送门
1 | def countDigitOne(self, n): |
分三种情况讨论比较清晰。
1 | def countDigitOne(self, n: int) -> int: |
45.把数组排成最小的数字
AcWing传送门
python2的写法。
1 | def PrintMinNumber(self, numbers): |
匿名函数作为sort的参数,在python2中有这个参数。
cmp specifies a custom comparison function of two arguments (iterable elements) which should return a negative, zero or positive number depending on whether the first argument is considered smaller than, equal to, or larger than the second argument: cmp=lambda x,y: cmp(x.lower(), y.lower()). The default value is None.
作为sort的参数,cmp提供了一个自定义的比较两个元素的方法,如果返回-1表示前者小于后者。python3中取消了这个参数,但是提供了一种key的转换。而内置函数可以通过运算符实现。
1 | cmp(a, b) |
等同于
1 | (a>b) - (a<b) |
所以python3的写法如下:
1 | from functools import cmp_to_key |
46 把数字翻译成字符串
LeetCode传送门
1 | def numDecodings(self, s: str) -> int: |
47 礼物的最大价值
LeetCode传送门。相似题目,求的是最小。
Acwing传送门这个是原题。
对首行使用初始化,然后消除了i的判断。
1 | def get_max_value(g: 'List[List[int]]') -> int: |
48.最长不含重复字符的子字符串
LeetCode传送门
方法二:找到重复值时,更新start的值,为什么使用max,因为start有可能大于dic[s[end]]+1
,比如当s='abba'
,end走到最后的时候,上一次start因为b做了更新变为了2。
1 | def lengthOfLongestSubstring(self, s): |
49.丑数
LeetCode传送门
1 | def nthUglyNumber(self, n: int) -> int: |
50.第一个只出现一次的字符
LeetCode传送门有一点小区别,LeetCode输出索引,书中输出值。
1 | s = "leetcode" |
Time-O(N), Space-O(N)。暂时没发现更快的算法了。
1 | def firstUniqChar(self, s): |
51.数组中的逆序对
牛客网传送门
AcWing传送门
这里使用了双端队列感觉不太合适,因为还要显式地转成list,否则没法对剩余的left或right做切片。也试了将其改为stack,但是stack来回reverse又太麻烦。
1 | def InversePairs(self, data): |
52.两个链表的第一个公共节点
LeetCode传送门
1 | def getIntersectionNode(self, headA, headB): |
53.在排序数组中查找数字
相似题目,LeetCode是求出数字的索引,书中返回个数。
LeetCode传送门。相似题目,LeetCode要返回两个索引,书中求个数。
1 | Input: nums = [5,7,7,8,8,10], target = 8 |
方法一:标准库写法。这里不需要target去整个nums中判断。
1 | def searchRange(self, nums: List[int], target: int) -> List[int]: |
方法二:自己实现。bisect_right的方式采用+1的形式。
1 | def searchRange(self, nums: List[int], target: int) -> List[int]: |
53.0~n-1中缺失的数字
LeetCode传送门
相似题目,LeetCode是未排序,书中是已排序。所以可以利用排序的特性使时间复杂度小于O(n)。即找出第一个下标与值不相等的元素,再-1就是缺失的元素。
AcWing传送门
方法一:数学公式。
1 | def missingNumber(self, nums): |
方法二:XOR.
index | 0 | 1 | 2 |
---|---|---|---|
value | 3 | 0 | 1 |
1 | def missingNumber(self, nums: 'List[int]') -> 'int': |
方法三:利用书中已排序的特性。
1 | def missingNumber(self, nums: List[int]) -> int: |
53 数组中数值和下标相等的元素
AcWing传送门
1 | def getNumberSameAsIndex(self, nums): |
54.二叉搜索树的第k大节点
AcWing传送门
注意:牛客网上是求第k小节点,这里被坑了一次,然后返回值居然要求返回节点对象,而不是节点值,这里的答案按书中返回。如果是牛客网上需要把节点添加到res
中,然后return res[k-1]
1 | def kth_largest(self, root: TreeNode, k: int) -> int: |
方法二:生成器写法,无比的清晰。
1 | def kthLargest(self, root: TreeNode, k: int) -> int: |
55.二叉树的深度
LeetCode传送门
1 | 3 |
方法一:recursively
1 | def max_depth(root): |
方法二:iteratively. BFS with deque
1 | def maxDepth(self, root: 'TreeNode') -> 'int': |
55.1平衡二叉树
LeetCode传送门
方法一:递归+递归。
1 | def isBalanced(self, root): |
方法二:上诉两种方法中都包含了一些无意义的重复遍历。这里采用后序遍历,边遍历边判断,不会重复节点。受此思想启发,添加一种后序遍历二叉树的方法。
1 | def isBalanced(self, root): |
方法三:dfs. 算深度的时候判断左右是否深度超过1. 这里变量不能把self去掉,否则[1,2,2,3,3,null,null,4,4]
会错误的返回True
而不是False
。也可以使用nonlocal
1 | def isBalanced(self, root: 'TreeNode') -> 'bool': |
56 数组中只出现一次的两个数字。
找出数组中两个唯一出现一次的元素,其余元素均出现两次。LeetCode传送门
1 | Input: [1,2,1,3,2,5] |
思想:将这两个元素分到两个组,由于这两个数不相等,所以亦或结果不为0,也就是说二进制中至少有一位1,记为第n位。我们以第n位是否为1,把数组分为两个子数组。
1 | def singleNumbers(self, nums: List[int]) -> List[int]: |
56.1数组中出现一次的数字,其余元素出现三次。
LeetCode传送门
1 | Input: [2,2,3,2] |
方法一:找出单独元素每一位的值。如果把所有数字的二进制每一位加起来,如果某一位可以被3整除,则表示单独元素的该位为0,否则为1。以下使用count
来表示每一位1
的个数。假设count%3!=0
为True,说明该元素i
位为1,然后是用|=
更新ans在第i
个位置的值,这里也可以使用+=
,但是效率稍慢。convert
的作用是因为python中的int是个对象,且没有最大限制,不是在第32位使用1来表示负数。
1 | def singleNumber(self, nums, n=3): |
1 | def singleNumber(self, nums): |
57.和为s的数字
牛客网传送门
AcWing传送门
看牛客网上的描述,如果有多对数字和为s,要求返回乘积最小的一对。乍一看以为牛客网又乱改题,但是仔细一想,如果两个和为s的数,而且是在递增数组中很明显,边缘的数字乘积要小,例如8X8>1X15
。所以还是和书中解法一样。
1 | def FindNumbersWithSum(self, array, tsum): |
57_1 和为s的连续正数序列
牛客网传送门
AcWing传送门
1 | def findContinuousSequence(self, tsum): |
58.翻转字符串。
LeetCode传送门
1 | Input: "the sky is blue", |
方法一:如果面试官是一个Pythoner,那么就让你过了。
1 | def reverse_words(s): |
如果你的面试官是一个只写Java或者C,看见代码就不平衡了,凭啥可以写到一行,非要你实现reverse。
1 | def reverseWords(self, s: str) -> str: |
实现split
,hp_reverse,
1 | def reverseWords(self, s: str) -> str: |
58_1 左旋转字符串
牛客网传送门
AcWing传送门
切片,书中的方法个人觉得Python并不适用。
1 | def LeftRotateString(self, s, n): |
59.滑动窗口的最大值。
LeetCode传送门
得益于python的切片。Time: O(n*k). k=n-size
1 | def maxInWindows(self, nums, size): |
方法二:队列保存索引。
1 | def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: |
60.n个骰子的点数
AcWing传送门
1 | [[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], [0, 1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]] |
dp[0][j]==1
表示第一个骰子和为j+1
的次数为1,因为数组下标从0开始。
1 | def dice_probability(n, val=6): |
感觉用dict来表示更加明确,没有数组下标从0开始的混淆。按照AcWing中的返回写出一种解法。
1 | from collections import defaultdict |
61.扑克牌中的顺子
牛客网传送门
AcWing传送门
开始以为还要用个dict来映射值,后来发现直接传得卡牌的值。思想就是先排序,然后查joker的数量,看剩下牌的差值加起来能不能用已有的joker连起来。
1 | def IsContinuous(self, numbers): |
只要没有重复的,并且最大的和最小的差<5就能组成顺子。
1 | def isStraight(self, nums: List[int]) -> bool: |
62.圆圈中最后剩下的数字
AcWing传送门
方法一:其实不需要环形链表,使用一个list足矣,每次将其旋转rot
位,一开始想将要把第m个数旋转到list首部,然后再pop(0)
,后来想到直接可以通过切片取出来,节省了pop(0)
的O(n)复杂度。
1 | def lastRemaining(self, n: int, m: int) -> int: |
方法二:书中的推导过程过于复杂,这里学到了一个稍微简单的推导过程。参考约瑟夫环问题。
1 | def LastRemaining_Solution(self, n, m): |
63.股票的最大利润
LeetCode传送门
方法一:Brute Force.其实就是求最高峰点和前面最低谷点的差。
1 | def maxProfit(self, prices: List[int]) -> int: |
方法二:标准的卡登算法。此题为53.连续数组最大和的变形,如果价格比之前小,则舍弃,否则一起计算连续子数组的和。
1 | def maxProfit(self, prices: List[int]) -> int: |
方法三:使用标准库的卡登算法。
1 | def maxProfit(self, prices: List[int]) -> int: |
64.求1+2+···+n
AcWing传送门
这题对python不是很友好,感觉and
也属于条件判断语句。reduce`
sum`之类的属于作弊行为,这里就不贴了。
1 | def Sum_Solution(self, n): |
65.不用加减乘除做加法
LeetCode传送门
此题由于Python中的int没有长度限制,在负数出现的情况,会导致结果与预期不同。详情见Python负数位运算
1 | def getSum(self, a, b): |
或者可以将其转成32位整数。
1 | import numpy as np |
66 构建乘积数组
LeetCode传送门
思路:不能使用除法。如书中所说,以i为分割点,将B拆成C,D两部分,左边是A[0] x A[1] x...x A[i-1]
右边则为A[i+1] x ...x A[n-1]
,C[i] = C[i-1] * A[i-1]
方法一:这里生成器没法直接使用reversed
,否则能达到O(1)
的空间复杂度。
1 | def constructArr(self, a: List[int]) -> List[int]: |
方法二:
不使用标准库的方法。
1 | def multiply(self, A): |
方法三:O(n)时间,O(1)空间。