目录
- 前言
- 剑指Offer11.旋转数组的最小数字
- 剑指Offer19.正则表达式匹配
- 剑指Offer20.表示数值的字符串
- 剑指Offer24.反转链表
- 剑指Offer26.树的子结构
- 剑指Offer34.二叉树中和为某一值的路径
- 剑指Offer35.复杂链表的复制
- 剑指Offer36.二叉搜索树与双向链表
- 剑指Offer37.序列化二叉树
- 剑指Offer40.最小的k个数
- 剑指Offer41.数据流中的中位数
- 剑指Offer43.1~n整数中1出现的次数
- 剑指Offer44.数字序列中某一位的数字
- 剑指Offer45.把数组排成最小的数
- 剑指Offer53-II.0~n-1中缺失的数字
- 剑指Offer51.数组中的逆序对
- 剑指Offer55-II.平衡二叉树
- 剑指Offer56-I.数组中数字出现的次数
- 剑指Offer56-II.数组中数字出现的次数II
- 剑指Offer57.和为s的两个数字
- 剑指Offer57-II.和为s的连续正数序列
- 剑指Offer62.圆圈中最后剩下的数字
- 剑指Offer65.不用加减乘除做加法
前言
经过面试发现一点很重要的问题,在面试的时候挺多时候考的就是手撕算法原题,这个时候需要注意两点:
- 不能表现出是死记硬背,而是有思考过程,知道每一步是为什么
- 要死记硬背。。。或者说对代码非常熟悉,在面试的时候能把所有细节都正确写出来才可以,面试官很强,知道重点是什么,也知道你代码写出来之后对不对,如果你记得不熟,就会出现问题,又不能运行调试,现看很难发现问题。
剑指Offer11.旋转数组的最小数字
这个题,反复看了很多次了,这次基本上算是自己做出来了,还有点磕磕绊绊,二分法确实是软肋。
class Solution:
def minArray(self, numbers: List[int]) -> int:
a = 0
b = len(numbers)-1
while a <= b:
m = (a + b) // 2
if numbers[m] < numbers[b]:
b = m # 不能b=m-1,因为这个时候numbers[m]可能就是最小值了,减一就跳到左边了
elif numbers[m] > numbers[b]:
a = m + 1 # 这里是正常的
else:
b -= 1 # 这个事情就很有意思,会发现numbers[m]==numbers[b],
# 就不知道最小值在numbers[m]的左边还是右边了,但是,
# 既然有这个等式,那就说明b可以左移一位,
# 因为即使numbers[b]是最小值,那numbers[m]也能是最小值
return numbers[a]
剑指Offer19.正则表达式匹配
这个题的状态转移实在是太复杂了。。。还有dp的循环起始点也很奇怪,做多少遍都不行。。orz
剑指Offer20.表示数值的字符串
我滴个亲娘。。。这状态转移要命啊,我放弃
剑指Offer24.反转链表
这题要是面试准得懵,其实逻辑很简单
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre, cur = None, head
while cur:
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
return pre
剑指Offer26.树的子结构
注意一个问题,如果这个结点不能满足,那么要继续判断左子树和右子树能不能满足。
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
if not A or not B:
return False
def f(A, B):
if not B:
return True
if not A:
return False
if A.val == B.val:
return f(A.left, B.left) and f(A.right, B.right)
else:
return False
return f(A,B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)
剑指Offer34.二叉树中和为某一值的路径
值得注意的是,记录路径时若直接执行res.append(path),则是将path对象加入了res;
后续path改变时,res中的path对象也会随之改变。
正确做法:res.append(list(path)) ,相当于复制了一个 path 并加入到 res 。
这个事情比较重要。。写的时候就是因为这个问题导致存了都是一样的list,然后还在变
剑指Offer35.复杂链表的复制
三步流程:
- 复制各节点,构建拼接链表
- 构建新链表各节点的 random 指向
- 拆分原 / 新链表
注意第二步,容易忽略,这个很重要!
剑指Offer36.二叉搜索树与双向链表
好像喜欢考那种需要很多细节的问题
这个题折过戟,一定要把握好
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
if not root:
return None
self.last = None
self.head = None
def f(root):
if not root:
return
f(root.left)
if not self.last:
self.head = root
else:
self.last.right = root
root.left = self.last
self.last = root
self.tail = root
f(root.right)
f(root)
self.head.left = self.tail
self.tail.right = self.head
return self.head
剑指Offer37.序列化二叉树
这个题就前序遍历输出和读取
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if not root:
return 'None'
return str(root.val) + ',' + str(self.serialize(root.left)) + ',' + str(self.serialize(root.right))
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
l = data.split(',')
def f(l):
r = l.pop(0)
if r == 'None':
return None
root = TreeNode(int(r))
root.left = f(l)
root.right = f(l)
return root
return f(l)
剑指Offer40.最小的k个数
三种解法:
- 排序后取前k个
- 堆排序,用大顶堆保存前k个,然后从k+1开始到后面,每一个判断一下是不是比堆顶要小,小的话就pop然后加入堆,最后返回这个堆就行了。注意:python没有大顶堆,只能取负后用小顶堆来做。
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
if k == 0:
return list()
hp = [-x for x in arr[:k]]
heapq.heapify(hp)
for i in range(k, len(arr)):
if -hp[0] > arr[i]:
heapq.heappop(hp)
heapq.heappush(hp, -arr[i])
ans = [-x for x in hp]
return ans
- 快排思想,每次分成两部分,比pivot小的在前面,比它大的在后面,有pivot的index,然后看index的位置,如果等于k了就返回前k个数就成了,不然还要分类一下。
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
def partition(arr,l,r):
pivot = arr[r]
i = l
for j in range(l,r+1):
if arr[j] <= pivot:
arr[i],arr[j] = arr[j],arr[i]
i += 1
return i-1
def sort(arr,l,r,k):
idx = partition(arr,l,r)
num = idx - l +1
if num == k:
return
elif num < k:
sort(arr, idx+1, r, k - num)
else:
sort(arr, l, idx-1, k)
if k == 0:
return []
sort(arr, 0, len(arr)-1,k)
return arr[:k]
剑指Offer41.数据流中的中位数
这个题,不得不说还是,秒哇!
弄俩堆,一个是小顶堆A,存大的那一半,另一个是大顶堆B,存小的那一半。
加数addNum:
- 如果A和B的长度相等,就往A里面加个数。方法是先加到B里,堆调整之后,再放A里;
- 如果A和B的长度不等,就往B里面加个数。方法是先加到A里,堆调整之后,再放B里。
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):
heapq.heappush(self.a, -heapq.heappushpop(self.b,-num))
else:
heapq.heappush(self.b, -heapq.heappushpop(self.a,num))
def findMedian(self) -> float:
if len(self.a) == len(self.b):
if not self.a:
return None
else:
return (self.a[0] - self.b[0]) / 2
else:
return self.a[0]
剑指Offer43.1~n整数中1出现的次数
得找到计算的公式,感觉面试不会考这种,没意思
class Solution:
def countDigitOne(self, n: int) -> int:
digit = 1
high = n//10
low = 0
cur = n % 10
total = 0
while high != 0 or cur != 0:
if cur == 0:
total += high * digit
elif cur == 1:
total += high * digit + 1 + low
else:
total += (high + 1) * digit
low += cur * digit
cur = high % 10
high //= 10
digit *= 10
return total
剑指Offer44.数字序列中某一位的数字
还是不会,服了
class Solution:
def findNthDigit(self, n: int) -> int:
digit, start, count = 1, 1, 9
while n > count: # 1.
n -= count
start *= 10
digit += 1
count = 9 * start * digit
num = start + (n - 1) // digit # 2.
return int(str(num)[(n - 1) % digit]) # 3.
剑指Offer45.把数组排成最小的数
快排
class Solution:
def minNumber(self, nums: List[int]) -> str:
def quick_sort(l , r):
if l >= r: return
i, j = l, r
while i < j:
while strs[j] + strs[l] >= strs[l] + strs[j] and i < j: j -= 1
while strs[i] + strs[l] <= strs[l] + strs[i] and i < j: i += 1
strs[i], strs[j] = strs[j], strs[i]
strs[i], strs[l] = strs[l], strs[i]
quick_sort(l, i - 1)
quick_sort(i + 1, r)
strs = [str(num) for num in nums]
quick_sort(0, len(strs) - 1)
return ''.join(strs)
剑指Offer53-II.0~n-1中缺失的数字
a是右边的第一个
b是左边最后一个
class Solution:
def missingNumber(self, nums: List[int]) -> int:
a,b = 0, len(nums)-1
while a <= b:
m = (a + b) // 2
if nums[m] == m:
a = m + 1
else:
b = m - 1
return a
剑指Offer51.数组中的逆序对
这个题就用归并排序来做吧,计数方式需要注意一下
按理说当nums[i] > nums[j]的时候说明会产生一个逆序对,但实际上写代码做不到这个过程
可以理解为当nums[i] <= nums[j]的时候,也就是nums[j-1]和之前的都小于nums[i]了,这个时候对前面的进行计数就可以了。
class Solution:
def mergeSort(self, nums, tmp, l, r):
if l >= r:
return 0
mid = (l + r) // 2
inv_count = self.mergeSort(nums, tmp, l, mid) + self.mergeSort(nums, tmp, mid + 1, r)
i, j, pos = l, mid + 1, l
while i <= mid and j <= r:
if nums[i] <= nums[j]:
tmp[pos] = nums[i]
i += 1
inv_count += (j - (mid + 1))
else:
tmp[pos] = nums[j]
j += 1
pos += 1
for k in range(i, mid + 1):
tmp[pos] = nums[k]
inv_count += (j - (mid + 1))
pos += 1
for k in range(j, r + 1):
tmp[pos] = nums[k]
pos += 1
nums[l:r+1] = tmp[l:r+1]
return inv_count
def reversePairs(self, nums: List[int]) -> int:
n = len(nums)
tmp = [0] * n
return self.mergeSort(nums, tmp, 0, n - 1)
剑指Offer55-II.平衡二叉树
最简单的办法就是遍历,然后每一次都算一下深度,左右子树深度超过1了就gg
最优解是后序遍历算深度+剪枝:
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def recur(root):
if not root: return 0
left = recur(root.left)
if left == -1: return -1
right = recur(root.right)
if right == -1: return -1
return max(left, right) + 1 if abs(left - right) <= 1 else -1
return recur(root) != -1
剑指Offer56-I.数组中数字出现的次数
剑指Offer56-II.数组中数字出现的次数II
位运算。。。到底需要不???
剑指Offer57.和为s的两个数字
双指针
剑指Offer57-II.和为s的连续正数序列
滑动窗口,还挺简单的
剑指Offer62.圆圈中最后剩下的数字
看看题解吧,理解了还挺对的
剑指Offer65.不用加减乘除做加法
位运算的问题还有补码