题目顺序
- [剑指 Offer 03. 数组中重复的数字 简单](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)
- [剑指 Offer 04. 二维数组中的查找 简单](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/)
- [剑指 Offer 05. 替换空格 简单](https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/)
- [剑指 Offer 06. 从尾到头打印链表 简单](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/)
- [剑指 Offer 07. 重建二叉树 中等](https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/)
- [剑指 Offer 09. 用两个栈实现队列 简单](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/)
- [剑指 Offer 10- I. 斐波那契数列 简单](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/)
- [剑指 Offer 10- II. 青蛙跳台阶问题 简单](https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/)
- [剑指 Offer 11. 旋转数组的最小数字 简单](https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/)
- [剑指 Offer 12. 矩阵中的路径 中等](https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/)
- [剑指 Offer 13. 机器人的运动范围 中等](https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/)
- [剑指 Offer 14- I. 剪绳子 中等](https://leetcode-cn.com/problems/jian-sheng-zi-lcof/)
- [剑指 Offer 14- II. 剪绳子 II 中等](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/)
- [剑指 Offer 15. 二进制中1的个数](https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/)
- [剑指 Offer 16. 数值的整数次方 中等](https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/)
- [剑指 Offer 18. 删除链表的节点 简单](https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/)
- [剑指 Offer 19. 正则表达式匹配 困难](https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof/)
- [剑指 Offer 20. 表示数值的字符串 中等](https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/)
- [剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 简单](https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/)
- [剑指 Offer 22. 链表中倒数第k个节点 简单](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/)
- [剑指 Offer 24. 反转链表 简单](https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/)
- [剑指 Offer 25. 合并两个排序的链表 简单](https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/)
- [剑指 Offer 26. 树的子结构 中等](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/)
- [剑指 Offer 27. 二叉树的镜像 简单](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/)
- [剑指 Offer 28. 对称的二叉树 简单](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/)
- [剑指 Offer 29. 顺时针打印矩阵 简单](https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/)
- [剑指 Offer 30. 包含min函数的栈](https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/)
- [剑指 Offer 31. 栈的压入、弹出序列 中等](https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/)
- [剑指 Offer 32 - I. 从上到下打印二叉树 中等](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/)
- [剑指 Offer 32 - III. 从上到下打印二叉树 III 中等](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/)
- [剑指 Offer 33. 二叉搜索树的后序遍历序列 中等](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/)
- [剑指 Offer 34. 二叉树中和为某一值的路径 中等](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/)
- [剑指 Offer 35. 复杂链表的复制 中等](https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/)
- [剑指 Offer 36. 二叉搜索树与双向链表 中等](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/)
- [剑指 Offer 37. 序列化二叉树 困难](https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/)
- [剑指 Offer 38. 字符串的排列 中等](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/)
- [剑指 Offer 39. 数组中出现次数超过一半的数字 简单](https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/)
- [剑指 Offer 40. 最小的k个数 简单](https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/)
- [剑指 Offer 41. 数据流中的中位数 困难](https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/)
- [剑指 Offer 42. 连续子数组的最大和 简单](https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/)
- [剑指 Offer 45. 把数组排成最小的数 中等](https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/)
- [剑指 Offer 46. 把数字翻译成字符串 中等](https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/)
- [剑指 Offer 46. 把数字翻译成字符串 中等](https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/)
- [剑指 Offer 47. 礼物的最大价值 中等](https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof/)
- [剑指 Offer 48. 最长不含重复字符的子字符串 中等](https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/)
- [剑指 Offer 49. 丑数 简单](https://leetcode-cn.com/problems/chou-shu-lcof/)
- [剑指 Offer 50. 第一个只出现一次的字符 简单](https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/)
- [剑指 Offer 51. 数组中的逆序对 困难](https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/)
剑指 Offer 03. 数组中重复的数字 简单
解法1:hash记录,如果重复直接退出,时间O(n),空间O(n)
解法2:鸽笼原理:判断i==(nums[i]=m),相等判断下一个数字,不相等则判断nums[i]==nums[m],出现重复,不相等调整nums[i]。时间O(n),空间O(1)
解法3:排序查找相同。时间O(nlogn),空间O(1)
hashmap = {}
for i in nums:
if i in hashmap:
return i
hashmap[i]=1
for i in range(len(nums)):
while nums[i] != i:
if nums[nums[i]] == nums[i]:
return nums[i]
nums[nums[i]] , nums[i] = nums[i] , nums[nums[i]]
剑指 Offer 04. 二维数组中的查找 简单
解法:站在右上角看,这个矩阵其实就像是一个Binary Search Tree。从右上角开始p,等于p返回,小于p在左边,大于p在下方。时间O(n*m),空间O(1)
if len(matrix)==0 or len(matrix[0])==0: return False
row,col = 0,len(matrix[0])-1
while row<len(matrix) and col>=0:
if matrix[row][col]>target:
col-=1
elif matrix[row][col]<target:
row+=1
else:
return True
return False
剑指 Offer 05. 替换空格 简单
解法1:从后面开始,遇到空格替换,减少空间使用,可以先扩充空格*2个空间。时间O(len(s),空间O(len(s))
解法2:从前面开始,遇到空格替换。时间O(len(s)),空间O(len(s))
res = ""
for i in range(len(s)-1,-1,-1):
if s[i]!=' ':
res = s[i]+res
else:
res = '%20'+res
return res
return s.replace(" ","%20")
剑指 Offer 06. 从尾到头打印链表 简单
解法:遍历链表,将每个值加入res,结果反转或是使用insert,使用栈先进后出的特性,全部读完后反向输出。时间空间O(n)
res = []
while head:
res.append(head.val)
head = head.next
return res[::-1]
剑指 Offer 07. 重建二叉树 中等
解法:根据先序划分左右子树,然后递归调用,时间空间O(n)
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not inorder:
return None
root_val = preorder.pop(0)
index_root = inorder.index(root_val)
root = TreeNode(root_val)
root.left = self.buildTree(preorder,inorder[:index_root])
root.right = self.buildTree(preorder,inorder[index_root+1:])
return root
剑指 Offer 09. 用两个栈实现队列 简单
解法:入栈保存在stack_in,出栈保存在stack_out,删除时将stack_in反向保存在out里。 时间空间O(n)
def __init__(self):
# 入栈和出栈分别维护
self.stack_in=[]
self.stack_out=[]
def appendTail(self, value: int) -> None:
self.stack_in.append(value)
def deleteHead(self) -> int:
if self.stack_out==[]:
if self.stack_in==[]:
return -1
else:
while self.stack_in:
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
剑指 Offer 10- I. 斐波那契数列 简单
解法:dp[i + 1] = dp[i] + dp[i - 1] dp[n],即斐波那契数列的第 n 个数字。
时间O(n) 空间O(1)
#Python 由于语言特性可以省去 sumsum 辅助变量和大数越界处理
a, b = 0, 1
for _ in range(n):
a, b = b, a+b
return a % 1000000007
剑指 Offer 10- II. 青蛙跳台阶问题 简单
解法:dp[i ] = dp[i-2] + dp[i - 1] 。
时间O(n) 空间O(1)
def numWays(self, n: int) -> int:
a,b=1,1
for _ in range(n):
temp = (a+b)%1000000007
a,b=b,temp
return a
剑指 Offer 11. 旋转数组的最小数字 简单
解法:二分判断可能无序的情况(右边),如果顺序,最小点在m处r=m;如果乱序,最小点在[m+1,r]l=m+1;相等的时候,不确定状况,从右边下来保守。时间O(logn)空间O(1)
l, r =0,len(numbers)-1
while l<r:
m = (l+r)//2
if numbers[m]<numbers[r]:
r=m
elif numbers[m]>numbers[r]:
l=m+1
else:
r-=1
return numbers[l]
剑指 Offer 12. 矩阵中的路径 中等
解法:DFS+递归,每次判断当前字母的合法性(索引越界,是否为当前查找值),使用递归判断下一个字母,直到长度为k=len(word)。时间O(3kmn)每次递归深度为k,每次3个(不走前路),因此每次时间为O(3k),可查找元素为m*n,空间O(k)
def dfs(i,j,k):
# 行、列越界,不相等
if not 0<=i<row or not 0<=j<col or board[i][j]!=word[k]: return False
if k==len(word)-1: return True
tmp, board[i][j] = board[i][j], '/'
# 在调用邻居时,保证当前不可访问
res = dfs(i-1,j,k+1) or dfs(i+1,j,k+1) or dfs(i,j-1,k+1) or dfs(i,j+1,k+1)
board[i][j] = tmp
return res
row,col = len(board),len(board[0])
for i in range(row):
for j in range(col):
if dfs(i,j,0): return True
return False
剑指 Offer 13. 机器人的运动范围 中等
即使是k=0,也能走一步(0,0).首先将(0,0)保存,每一步只能向下或向右走,只需判断当前数位和是否合法。时空O(mn)
def digitsum(n):
n = 0
while(v):
n+=v%10
v = v//10
return n
def movingCount(self, m: int, n: int, k: int) -> int:
vis = set([(0, 0)])
for i in range(m):
for j in range(n):
if ((i - 1, j) in vis or (i, j - 1) in vis) and digitsum(i) + digitsum(j) <= k:
vis.add((i, j))
return len(vis)
剑指 Offer 14- I. 剪绳子 中等
尽可能的剪出3和2,3的优先级最高.将绳子 .以相等的长度等分为多段 ,得到的乘积最大n=3a+b,b=0是,res=3a;b=1时,res=3a-1x2x2;b=2时,res=3ax2
if n<=3:
return n-1
s = 1
while n>4:
s*=3
n-=3
return s*n
剑指 Offer 14- II. 剪绳子 II 中等
区别是大整数溢出,需要取余:快速幂求余
a,b,x,p,rem=n//3-1,n%3,3,1000000007,1
while a>0: # (x^a-1^) % p
# 奇数
if a%2: rem = (rem*x)%p
x = x**2%p
a = a//2
if b==0:
return (rem*3)%p
elif b==1:
return (rem*4)%p
else:
return (rem*6)%p
剑指 Offer 15. 二进制中1的个数
1&1=1 1&0=0,时间O(log2n),空间O(1)
res = 0
while n:
res += n&1
n>>=1
return res
剑指 Offer 16. 数值的整数次方 中等
1&1=1 1&0=0,时间O(log2n),空间O(1)
def myPow(self, x: float, n: int) -> float:
if x==0:
return 0
if n<0: x,n=1/x,-n
rem = 1
while n>0:
if n%2:rem *= x
x *=x
n//=2
return rem
剑指 Offer 18. 删除链表的节点 简单
使用哑节点
def deleteNode(self, head: ListNode, val: int) -> ListNode:
dummy = ListNode(float('inf'))
dummy.next = head
pre,p = dummy,dummy.next
while p:
if p.val == val:
pre.next = p.next
break
pre,p = p,p.next
return dummy.next
剑指 Offer 19. 正则表达式匹配 困难
当前在(i,j)主要考虑的是✳号:分为两种可能,匹配前面字符0次,转移(i,j+2);匹配一次,转移s[i]=p[j] and(i+1,j)
def isMatch(self, s: str, p: str) -> bool:
memo = {}
def dp(i,j):
# 记在备忘录
if (i,j) in memo: return memo[(i,j)]
# j超出pattern时,若i此时也超出s,则成功,否则失败
if j==len(p): return i==len(s)
# j没有超出,i没有超出,现在匹配第一个是否成功(第一个有两种可能,‘.’或是‘s[i]’)
first_match = i<len(s) and p[j] in [s[i],'.']
# 判断‘*’的存在
if j<=len(p)-2 and p[j+1]=='*':
# 匹配0次(pj走两步,si不走) 或 匹配当前字符s[i]一次(si走一步,p不走)
ans = dp(i,j+2) or (first_match and dp(i+1,j))
else:
# 没有‘*’,直接判断第一个字符
ans = first_match and dp(i+1,j+1)
memo[(i,j)]=ans
return ans
return dp(0,0)
剑指 Offer 20. 表示数值的字符串 中等
状态机states[i] ,其中 i 为所处状态, states[i]使用哈希表存储可转移至的状态
def isNumber(self, s: str) -> bool:
# states[i] ,其中 i 为所处状态, states[i]使用哈希表存储可转移至的状态
# A[.[B]][e/EC]
# 从开始可能的状态 ' ':继续作为开始, 's':e之前的符号, 'd':数字, '.':小数点
# e之前的符号s:可转移状态有'd' '.'
# A是小数点之前的整数:可转移状态有'd', '.' , 'e', ' '
# B是小数点之后的数字:可转移状态有'd', 'e', ' '
# 开始之后直接是小数点:可转移状态有'd'
# e 可转移状态有's', 'd'
# e之后的符号:可转移状态有'd'
# C是e之后的数字:可转移状态有'd', , ' '
# 结束空格,只能一直空格到末尾:转移状态' '
states = [
{ ' ': 0, 's': 1, 'd': 2, '.': 4 }, # 0. start with 'blank' 空格开始
{ 'd': 2, '.': 4 } , # 1. 'sign' before 'e' 出现在e之前的符号
{ 'd': 2, '.': 3, 'e': 5, ' ': 8 }, # 2. 'digit' before 'dot' 出现在小数点之前的数字
{ 'd': 3, 'e': 5, ' ': 8 }, # 3. 'digit' after 'dot' 出现在小数点之后的数字
{ 'd': 3 }, # 4. 'digit' after 'dot' (‘blank’ before 'dot') 空格后直接就是小数点,小数点之后的数字
{ 's': 6, 'd': 7 }, # 5. 'e' 出现e
{ 'd': 7 }, # 6. 'sign' after 'e' e之后的符号
{ 'd': 7, ' ': 8 }, # 7. 'digit' after 'e' e之后的数字
{ ' ': 8 } # 8. end with 'blank' 结束空格符
]
p = 0 # start with state 0
for c in s:
# c是下一个转移状态
if '0' <= c <= '9': t = 'd' # digit
elif c in "+-": t = 's' # sign
elif c in ".eE ": t = c # dot, e, blank
else: t = '?' # unknown
# t不在哈希表 states[p]中,说明无法转移至下一状态,因此直接返回 False
if t not in states[p]: return False
p = states[p][t]
return p in (2,3,7,8)
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 简单
左右两个指针,如果左为奇,则左指针前移;如果右为偶,则右指针后移;如果左指向数组元素为偶,右指向数组元素为奇,互相交换位置
def exchange(self, nums: List[int]) -> List[int]:
i,j =0, len(nums)-1
while i<j:
# 找偶数
if nums[i]&1==1:
i+=1
# 找奇数
elif nums[j]&1==0 and i<j:
j-=1
else:
nums[i],nums[j] = nums[j],nums[i]
i+=1
j-=1
return nums
剑指 Offer 22. 链表中倒数第k个节点 简单
双指针:指针p走k步;然后两个指针p,q一起走,p走到头时,q走了lenth-k步,即倒数第k个
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
p,q=head,head
while k:
p = p.next
k-=1
while p:
p, q = p.next, q.next
return q
剑指 Offer 24. 反转链表 简单
新建尾结点q=None,从头遍历链表,每次将头结点指向尾结点,并将头结点指向下一个节点,尾结点更新为当前节点
def reverseList(self, head: ListNode) -> ListNode:
q = None
while head:
temp = head.next
head.next = q
q = head
head = temp
return q
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
dummy = ListNode(0)
L = dummy
while l1 and l2:
if l1.val<=l2.val:
L.next = l1
l1=l1.next
else:
L.next = l2
l2=l2.next
L = L.next
if l1:
L.next = l1
if l2:
L.next = l2
return dummy.next
剑指 Offer 25. 合并两个排序的链表 简单
哑节点
时空O(M+N)
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
dummy = ListNode(0)
L = dummy
while l1 and l2:
if l1.val<=l2.val:
L.next = l1
l1=l1.next
else:
L.next = l2
l2=l2.next
L = L.next
if l1:
L.next = l1
if l2:
L.next = l2
return dummy.next
剑指 Offer 26. 树的子结构 中等
递归匹配,使用原函数找到第一个匹配点B的根节点;递归函数匹配B的左右子树
时间O(MN):先序遍历A树时间O(M),每次调用递归函数O(N)。空间O(M),空间栈占用的A的节点
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
def Recur(A,B):
# 当节点 B 为空:说明树 B 已匹配完成
if not B:return True
# 当节点A为空,B不为空或是A!=B,匹配错误,
if not A or A.val!=B.val:return False
# 匹配左右子树
return Recur(A.left,B.left) and Recur(A.right,B.right)
# 特例是第一次传参的A,B为空时错误.找到第一个匹配点后会调用
return bool( A and B) and (Recur(A,B) or self.isSubStructure(A.left,B) or self.isSubStructure(A.right,B))
剑指 Offer 27. 二叉树的镜像 简单
使用队列,将每一层由左至右记录下来,并将每个节点的左右置换,时间O(n),空间O(最大行)
def mirrorTree(self, root: TreeNode) -> TreeNode:
if not root:return root
queue = collections.deque([root])
while queue:
lenth = len(queue)
for i in range(lenth):
node = queue.popleft()
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
node.left, node.right = node.right, node.left
return root
剑指 Offer 28. 对称的二叉树 简单
递归调用,每次判断左左-右右,左右-右左
时间O(N),最多调用N/2个节点;空间O(N),最差的链表状态占用空间O(N)
def isSymmetric(self, root: TreeNode) -> bool:
return self.check(root,root)
def check(self,p,q):
if not p and not q:return True
elif p and q and p.val==q.val:
return self.check(p.left,q.right) and self.check(p.right,q.left)
else:
return False
剑指 Offer 29. 顺时针打印矩阵 简单
按照顺时针打印,左->右(上)->下(右)->左(下)->上,判断是否到达边界
时间O(MN) 空间O(1)
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix: return []
left,right,up,down,res,i = 0,len(matrix[0])-1,0,len(matrix)-1,[],0
while True:
# left to right 上边界 up 加 1 up>down
for i in range(left,right+1): res.append(matrix[up][i])
up+=1
if up>down: break
# up to down 右边界 right减 1 right<left
for i in range(up,down+1): res.append(matrix[i][right])
right-=1
if left>right: break
# right to left 下边界 down减 1 up>down
for i in range(right,left-1,-1): res.append(matrix[down][i])
down-=1
if up>down: break
# down to up 左边界 left加 1 right<left
for i in range(down,up-1,-1): res.append(matrix[i][left])
left+=1
if left>right: break
return res
剑指 Offer 30. 包含min函数的栈
使用min_stack存储当前最小值
时间O(1),空间O(N)
class MinStack:
import math
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = []
self.min_stack = [math.inf]
def push(self, x: int) -> None:
self.stack.append(x)
self.min_stack.append(min(x,self.min_stack[-1]))
def pop(self) -> None:
if len(self.stack)==0:
return None
self.stack.pop()
self.min_stack.pop()
def top(self) -> int:
return self.stack[-1]
def min(self) -> int:
return self.min_stack[-1]
剑指 Offer 31. 栈的压入、弹出序列 中等
使用一个栈模拟入栈出栈操作,如果栈顶是出栈元素,出栈并且出栈元素向后移动,否则入栈
时空O(N)
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
stack = []
i=0
for num in pushed:
stack.append(num)
while stack and stack[-1]==popped[i]:
stack.pop()
i+=1
return not stack
剑指 Offer 32 - I. 从上到下打印二叉树 中等
层次遍历
时间O(N) 空间O(N)
def levelOrder(self, root: TreeNode) -> List[int]:
if not root:return []
queue = collections.deque([root])
res = []
while queue:
node = queue.popleft()
res.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return res
剑指 Offer 32 - III. 从上到下打印二叉树 III 中等
层次遍历+记录行数
时间O(N) 空间O(N)
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:return []
h=0
queue = collections.deque([root])
res = []
while queue:
layer = []
lenth = len(queue)
for i in range(lenth):
node = queue.popleft()
layer.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
# 奇数层,逆序
if h&1:
res.append(layer[::-1])
else:
res.append(layer)
h+=1
return res
剑指 Offer 33. 二叉搜索树的后序遍历序列 中等
二叉搜索树:左<中<右,左子树始终小于root和右子树。整个树是float(‘inf’)的左子树,现将右子树以及根入栈,遇到左子树时,出栈并更新当前root。若左子树存在比root大的数,直接False
时空O(N)
def verifyPostorder(self, postorder: List[int]) -> bool:
root,stack=float('inf'),[]
for i in range(len(postorder)-1,-1,-1):
if postorder[i]>root:return False
while stack and stack[-1]>postorder[i]:
root = stack.pop()
stack.append(postorder[i])
return True
剑指 Offer 34. 二叉树中和为某一值的路径 中等
中序遍历,每次判断当前节点是否为叶子结点以及sum==0,成功记录路径;遍历其左右节点。遍历完成当前节点,路径删除
时空O(N)
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
res,path = [],[]
def dfs(node,s):
if not node:return
path.append(node.val)
s -= node.val
if s==0 and node.left==None and node.right==None:
res.append(list(path))
dfs(node.left,s)
dfs(node.right,s)
path.pop()
dfs(root,sum)
return res
剑指 Offer 35. 复杂链表的复制 中等
每个节点复制之后放在当前节点之后,复制random,最后拆分链表
时间O(N) 空间O(1)
def copyRandomList(self, head: 'Node') -> 'Node':
if not head: return head
cur = head
while cur:
new_node = Node(cur.val,None,None)
new_node.next = cur.next
cur.next=new_node
cur = new_node.next
cur = head
while cur:
cur.next.random = cur.random.next if cur.random else None
cur = cur.next.next
old, new = head, head.next
new_head = head.next
while old:
old.next = old.next.next
new.next = new.next.next if new.next else None
old, new = old.next, new.next
return new_head
剑指 Offer 36. 二叉搜索树与双向链表 中等
Left:prev right:next 中序遍历最后是尾结点,记录头结点head以及前节点prev,更改头结点与尾结点的l,r,对节点node处理prev.right=node;node.left=prev 。解法1:使用递归。解法2:使用堆栈
时间O(N) 空间O(N)
def treeToDoublyList(self, root: 'Node') -> 'Node':
def dfs(node):
if not node:return
dfs(node.left)
if self.pre==None:
# 头结点
self.head = node
else:
node.left = self.pre
self.pre.right = node
self.pre = node
dfs(node.right)
if not root:return
self.pre = None
dfs(root)
self.head.left = self.pre
self.pre.right = self.head
return self.head
def treeToDoublyList(self, root: 'Node') -> 'Node':
if not root:return
stack = []
pre=None
while stack or root:
if root:
stack.append(root)
root = root.left
else:
node = stack.pop()
if pre==None:
head = node
else:
pre.right = node
node.left = pre
pre = node
root = node.right
head.left=pre
pre.right=head
return head
剑指 Offer 37. 序列化二叉树 困难
层次遍历:正向反向
时间O(N) 空间O(N)
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if not root: return "[]"
res=[]
queue = collections.deque([root])
while queue:
node = queue.popleft()
if node:
res.append(str(node.val))
queue.append(node.left)
queue.append(node.right)
else:
res.append('null')
return "["+','.join(res)+"]"
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if data=="[]":return None
vals = data[1:-1].split(',')
i=1
root = TreeNode(vals[0])
queue = collections.deque([root])
while queue:
node = queue.popleft()
if vals[i]!='null':
node.left = TreeNode(vals[i])
queue.append(node.left)
i+=1
if vals[i]!='null':
node.right = TreeNode(vals[i])
queue.append(node.right)
i+=1
return root
剑指 Offer 38. 字符串的排列 中等
全排列,固定第x位,固定x+1位,一直到最后一位存储c.第x位[x,n-1],第x+1范围是[x+1,n-1]
时间O(N!) 空间O(N2)
def permutation(self, s: str) -> List[str]:
c, n, res = list(s), len(s), []
def dfs(x):
if x==n-1:
res.append("".join(c))
dic = set()
for i in range(x,n):
if c[i] in dic:continue
dic.add(c[i])
c[i], c[x] = c[x], c[i]
dfs(x+1)
c[i], c[x] = c[x], c[i]
dfs(0)
return res
剑指 Offer 39. 数组中出现次数超过一半的数字 简单
解法1:需要的数字出现次数多于一半 那么排序后必定在中间
时间O(NlogN) 空间O(1)
def majorityElement(self, nums: List[int]) -> int:
nums.sort()
return nums[len(nums)//2]
解法2:摩尔投票,由于众数出现的次数超过数组长度的一半;若记 众数 的票数为 +1+1 ,非众数 的票数为 -1−1 ,则一定有所有数字的 票数和 > 0>0
时间O(N) 空间O(1)
def majorityElement(self, nums: List[int]) -> int:
votes=0
for num in nums:
if votes==0: x = num
votes += 1 if num==x else -1
count=0
for num in nums:
if num==x: count+=1
return x if count>len(nums)//2 else 0
剑指 Offer 40. 最小的k个数 简单
解法:快速选择,类似于快速排序的分治法
时间O(N),最坏会退化到O(n^2) 空间O(N)
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
# 快速选择
n = len(arr)
if n==0 or k>n: return
if k==0: return []
def quickselect(l,r):
pivot = arr[l]
low = l+1
high = r
while True:
while low<=high and arr[low]<=pivot:
low+=1
while high>=low and arr[high]>=pivot:
high-=1
if low>high:
break
arr[low], arr[high] = arr[high], arr[low]
arr[l], arr[high]=arr[high], arr[l]
return high
l, r = 0, len(arr)-1
while l<=r:
m = quickselect(l,r)
if m==k:
break
elif m<k:
l = m+1
else:
r = m-1
return arr[:k]
剑指 Offer 41. 数据流中的中位数 困难
解法:插入数字的排序,使用A是小顶堆,保存较大的那一半,如果是奇数,多保存一个元素,B是大顶堆,保存较小的那一半。向A中插入元素,先在B里比较,然后较小一半的最大值放在A堆
插入时间O(logN) ,访问时间O(1)空间O(N)
class MedianFinder:
def __init__(self):
"""
initialize your data structure here.
"""
self.A = []
self.B = []
def addNum(self, num: int) -> None:
if len(self.A)==len(self.B):
heappush(self.B,-num)
heappush(self.A,-heappop(self.B))
else:
heappush(self.A,num)
heappush(self.B,-heappop(self.A))
def findMedian(self) -> float:
return self.A[0] if len(self.A)!=len(self.B) else (self.A[0]-self.B[0])/2.0
剑指 Offer 42. 连续子数组的最大和 简单
解法:对于第i个数字要么自成一派,要么和前面的子数组和合并 dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);
插入时间O(N)空间O(1)
def maxSubArray(self, nums: List[int]) -> int:
res= nums[0]
for i in range(1,len(nums)):
if nums[i-1]>0:
nums[i] += nums[i-1]
res = max(res,nums[i])
return res
剑指 Offer 45. 把数组排成最小的数 中等
解法:字符串的拼接规则,若x+y>y+x:则x>y;
插入时间O(NlogN)空间O(N)
def minNumber(self, nums: List[int]) -> str:
def sort_rule(x,y):
a, b = x+y, y+x
if a>b: return 1
elif b>a: return -1
else: return 0
strs = [str(num) for num in nums]
strs.sort(key = functools.cmp_to_key(sort_rule))
return "".join(strs)
剑指 Offer 46. 把数字翻译成字符串 中等
解法:翻译方法增加点在于10-25之间两种状态,dp[i]:0-i之间的翻译方法。如果num_l[i-1], num_l[i]处于10-25,那当前的翻译方式由dp[i-1]和dp[i-2]得到。举例:122 指向最后一个2时,dp[2] = dp1+dp0
时间O(logN)时间复杂度是字符串的长度 空间O(logN)
def translateNum(self, num: int) -> int:
num_l = list(map(int,str(num)))
if len(num_l)<=1:
return len(num_l)
def helper(a: int, b:int) -> bool:
x = a*10+b
return x>=10 and x<=25
# dp
dp = [0 for _ in num_l]
# base case
dp[0] = 1
dp[1] = 2 if helper(num_l[0],num_l[1]) else 1
for i in range(2,len(num_l)):
if helper(num_l[i-1], num_l[i]):
dp[i] = dp[i-1] + dp[i-2]
else:
dp[i] = dp[i-1]
return dp[-1]
剑指 Offer 46. 把数字翻译成字符串 中等
解法1:翻译方法增加点在于10-25之间两种状态,dp[i]:0-i之间的翻译方法。如果num_l[i-1], num_l[i]处于10-25,那当前的翻译方式由dp[i-1]和dp[i-2]得到。举例:122 指向最后一个2时,dp[2] = dp1+dp0
解法2:每次判断最后两位,如果最后两位处于10-25则有两个选择num/10+num/100进行递归;如果不是那只有一种选择num/10
时间O(N) 时间复杂度是字符串的长度 空间O(N)
def translateNum(self, num: int) -> int:
num_l = list(map(int,str(num)))
if len(num_l)<=1:
return len(num_l)
def helper(a: int, b:int) -> bool:
x = a*10+b
return x>=10 and x<=25
# dp
dp = [0 for _ in num_l]
# base case
dp[0] = 1
dp[1] = 2 if helper(num_l[0],num_l[1]) else 1
for i in range(2,len(num_l)):
if helper(num_l[i-1], num_l[i]):
dp[i] = dp[i-1] + dp[i-2]
else:
dp[i] = dp[i-1]
return dp[-1]
public int translateNum(int num) {
if (num<=9) {return 1;}
//获取输入数字的余数,然后递归的计算翻译方法
int ba = num%100;
//如果小于等于9或者大于等于26的时候,余数不能按照2位数字组合,比如56,只能拆分为5和6;反例25,可以拆分为2和5,也可以作为25一个整体进行翻译。
if (ba<=9||ba>=26) {return translateNum(num/10);}
// ba=[10, 25]时,既可以当做一个字母,也可以当做两个字母
else {return translateNum(num/10)+translateNum(num/100);}
}
}
剑指 Offer 47. 礼物的最大价值 中等
** 解法:dp[i][j]=max(dp[i -1][j], dp[i][j - 1]) + grid[i - 1][j - 1],注意边界判断或是空间换时间减少边界判断 **
时间O(MN) 空间O(MN)
def maxValue(self, grid: List[List[int]]) -> int:
row, col = len(grid),len(grid[0])
dp = [[0 for j in range(col+1)] for i in range(row+1)]
for i in range(1,row+1):
for j in range(1,col+1):
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
return dp[row][col]
剑指 Offer 48. 最长不含重复字符的子字符串 中等
解法:滑动窗口
时间O(N) 空间O(N)
def lengthOfLongestSubstring(self, s: str) -> int:
if len(s)<=1:
return len(s)
windows = {}
left, right, valid= 0, 0, 0
lenth = 0
while right<len(s):
c = s[right]
right+=1
windows[c] = windows.get(c, 0) + 1
# 判断左侧窗口是否要收缩:出现重复字符,left+1
while windows[c]>1:
d = s[left]
left+=1
# 进行窗口内数据的一系列更新
windows[d] -= 1
lenth = max(lenth,right-left)
return lenth
剑指 Offer 49. 丑数 简单
解法:任意一个丑数都是由小于它的某一个丑数*2,3或者5得到的,所有丑数的排列,必定就是3个数组的合并结果然后去重得到的
时间O(N) 空间O(N)
def nthUglyNumber(self, n: int) -> int:
dp, a,b,c=[1]*n,0,0,0
for i in range(1,n):
n2,n3,n5 = dp[a]*2,dp[b]*3,dp[c]*5
dp[i] = min(n2,n3,n5)
if dp[i]==n2:a+=1
if dp[i]==n3:b+=1
if dp[i]==n5:c+=1
return dp[n-1]
剑指 Offer 50. 第一个只出现一次的字符 简单
解法:Python 3.6 后,默认字典就是有序的,因此无需使用 OrderedDict()
时间O(N) 空间O(N)
def firstUniqChar(self, s: str) -> str:
dic = {}
for c in s:
dic[c] = not c in dic
for c in s:
if dic[c]: return c
return ' '
剑指 Offer 51. 数组中的逆序对 困难
解法:如果左边部分的数组第l个元素大于右边第r个元素,那么左边数组将有(n1-l)个都大于右边第r个元素
时间O(NlogN) 空间O(N)
def merge(self,arr1,arr2) -> List[int]:
l,r=0,0
n1,n2=len(arr1),len(arr2)
res = []
while l<n1 and r<n2:
if arr1[l]>arr2[r]:
self.ans+=(n1-l)
res.append(arr2[r])
r+=1
else:
res.append(arr1[l])
l+=1
return res+arr1[l:]+arr2[r:]
def merge_sort(self,arr: List[int]) -> List[int]:
n = len(arr)
if n<=1: return arr
left = self.merge_sort(arr[:n//2])
right = self.merge_sort(arr[n//2:])
return self.merge(left,right)
def reversePairs(self, nums: List[int]) -> int:
self.ans = 0
self.merge_sort(nums)
return self.ans
剑指 Offer 52. 两个链表的第一个公共节点 简单
解法:两个链表长度分别为L1+C、L2+C, C为公共部分的长度. 第一个人走了L1+C步后,回到第二个人起点走L2步;第2个人走了L2+C步后,回到第一个人起点走L1步。 当两个人走的步数都为L1+L2+C时就两个家伙就相爱了
时间O(L1+l2+c)空间O(1)
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
p, q = headA, headB
while p!=q:
p = headB if p==None else p.next
q = headA if q==None else q.next
return p
剑指 Offer 56 - I. 数组中数字出现的次数 中等
解法:首先获取所有数字的xor,不相同的两个数字的最低位保存在diff,与diff一致类型保存在b,不同的保存在a
时间空间O(1)
def singleNumbers(self, nums: List[int]) -> List[int]:
res = 0
for num in nums:
res ^= num
diff = res&(-res)
a, b = 0, 0
for num in nums:
# 与差距最低位不一致
if num&diff:
a ^= num
# 与差距最低位一致
else:
b ^= num
return [a,b]