面试题答案
一键面试可能出现性能问题的情况
- 频繁的函数调用:
- 举例:在循环内部频繁调用函数,尤其是自定义函数,会带来额外的开销。
def some_function(x): return x * 2 result = [] for i in range(100000): result.append(some_function(i))
- 原因:函数调用涉及到栈操作、参数传递等开销,在循环中频繁进行会显著降低性能。
- 重复的计算:
- 举例:在循环中重复计算相同的结果,而不进行缓存。
import math for i in range(10000): value = math.sqrt(25) # 这里每次循环都计算math.sqrt(25),实际结果不变 result = i * value
- 原因:浪费了计算资源,每次循环都重新计算相同的结果。
- 低效的数据结构操作:
- 举例:在列表头部插入元素。
my_list = [] for i in range(10000): my_list.insert(0, i)
- 原因:列表在头部插入元素的时间复杂度是O(n),每次插入都需要移动后续元素,随着循环次数增加,性能急剧下降。
- 不必要的循环嵌套:
- 举例:过度嵌套循环,增加了计算量。
for i in range(100): for j in range(100): result = i + j
- 原因:嵌套循环的时间复杂度是O(n * m),比单个循环的时间复杂度高,增加了不必要的计算。
对应的解决方法
- 减少函数调用次数:
- 方法:如果可能,将函数调用移到循环外部。对于上述
some_function
的例子,可以这样优化:
def some_function(x): return x * 2 func = some_function result = [] for i in range(100000): result.append(func(i))
- 解释:提前获取函数对象,减少每次循环中查找函数的开销。对于简单函数,还可以使用
lambda
表达式并结合map
等函数,例如result = list(map(lambda x: x * 2, range(100000)))
。
- 方法:如果可能,将函数调用移到循环外部。对于上述
- 缓存重复计算结果:
- 方法:将重复计算的结果在循环外计算一次并保存。对于
math.sqrt(25)
的例子:
import math sqrt_25 = math.sqrt(25) for i in range(10000): result = i * sqrt_25
- 解释:只计算一次
math.sqrt(25)
,避免了在循环中的重复计算。
- 方法:将重复计算的结果在循环外计算一次并保存。对于
- 使用合适的数据结构:
- 方法:如果需要在头部插入元素,可以使用
collections.deque
。对于上述列表头部插入元素的例子:
from collections import deque my_deque = deque() for i in range(10000): my_deque.appendleft(i)
- 解释:
deque
在头部插入元素的时间复杂度是O(1),相比列表在头部插入的O(n)性能有很大提升。
- 方法:如果需要在头部插入元素,可以使用
- 优化循环嵌套:
- 方法:检查是否可以将嵌套循环合并或简化。对于上述过度嵌套循环的例子,如果只是需要计算
i + j
的所有组合,可以考虑使用列表推导式或itertools.product
。 - 列表推导式方法:
results = [i + j for i in range(100) for j in range(100)]
itertools.product
方法:
from itertools import product results = [i + j for i, j in product(range(100), range(100))]
- 解释:列表推导式和
itertools.product
在一定程度上优化了代码结构,可能带来性能提升,并且代码更简洁。同时,如果可能,分析是否真的需要嵌套循环,看能否通过其他算法或逻辑简化。
- 方法:检查是否可以将嵌套循环合并或简化。对于上述过度嵌套循环的例子,如果只是需要计算