Python面试篇(二)

简介

  • 上一篇介绍了基础的面试技巧和Python语言考察点,本篇主要从常用算法和数据结构入手

算法和数据结构

  • 常用内置数据结构和算法
    py1
    • 数据结构和算法是不分家的
    • 单纯的看数据结构,就是数据存储的格式,这里的线性、链式、KV、集合等等
    • 我们更关心基于存储格式的算法,列表、字典、集合属性,内置库算法
  • 常用库collections
    方法 作用
    namedtuple() 创建命名元组子类的工厂函数
    deque 类似列表(list)的容器,实现了在两端快速添加(append)和弹出(pop)
    ChainMap 类似字典(dict)的容器类,将多个映射集合到一个视图里面
    Counter 字典的子类,提供了可哈希对象的计数功能
    OrderedDict 字典的子类,保存了他们被添加的顺序
    defaultdict 字典的子类,提供了一个工厂函数,为字典查询提供一个默认值
    UserDict 封装了字典对象,简化了字典子类化
    UserList 封装了列表对象,简化了列表子类化
    UserString 封装了列表对象,简化了字符串子类化
    • 看个例子:
    import collections
    # collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
    # 返回一个新的元组子类,名为typename ;简而言之,它让tuple更可读
    _some = collections.namedtuple('Point', ['x', 'y'])	# 这个Point不是关键,重点是让下标从01变为xy,更可读
    p = _some(11, y=22)
    p[0] + p[1]	# 33
    
    # 双端队列,两头插
    de = collections.deque()
    de.append(1)
    de.append(2)
    de.appendleft(0)
    de	# deque([0, 1, 2]) 
    de.pop()
    de.popleft()
    
    # 计数器
    c = collections.Counter('abababaccd')
    c	# Counter({'a': 4, 'b': 3, 'c': 2, 'd': 1}) 
    c.most_common()	# [('a', 4), ('b', 3), ('c', 2), ('d', 1)] 
    
    # OrderedDict
    order = collections.OrderedDict()	# 保存了插入时的顺序
    order['a'] = 1
    order['b'] = 2
    order['c'] = 3
    list(order.keys())	# ['a', 'b', 'c']   方便实现LRU Cache
    
  • 看官方文档是个好主意

dict底层结构

  • 字典使用哈希表作为底层结构
  • 平均查找时间复杂度O(1)
  • 二次探查解决哈希冲突问题
    • 解决哈希冲突有三种方法:开放定址法、再哈希法、链地址法
    • 开放定址法有通用的再散列函数(Hi=(H(key)+di)%m i=1,2,…,n)
    • 其中,二次探测再散列:冲突发生时,在表的左右进行跳跃式探测(di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 ),正负代表左右两侧)
    • 链地址法:冲突后使用链表保存相同key的元素
  • 使用哈希结构的另一个问题是如何扩容
    • 类似C++中vector的容量扩张方法
    • Hash表中每次发现loadFactor==1(负载因子:used/size)时,就开辟一个原来桶数组的两倍空间(称为新桶数组)
    • 把原来的桶数组中元素所有转移过来到新的桶数组中
    • 以上是针对链地址法的扩容操作,缺点是要移动所有元素
  • 哈希表的底层使用数组(嗯,简单吧,不然怎么O(1),会直接将key按照算法转换成数组下标)

list/tuple

  • 都是线性结构,支持下标访问
  • list是可变对象,tuple保存的引用不可变
    • 不可变指的是不能替换掉里面的对象
    • 但如果这个对象本身就是可变的,可以修改
    t  = ([1],2,3)
    t[2] = 4	# TypeError: 'tuple' object does not support item assignment
    t[0].append(2)
    
  • list没法作为字典的key,tuple可以(可变对象不可hash)
  • list的本质还是数组,但由于能存储不同类型的对象,保存的是指向对象的指针,即这是个指针数组

实现LRU

  • 这是一种缓存剔除策略(最近最少使用),学过OS的应该都知道,常见的还有LFU
  • 原理呢?使用循环双端队列更新,把最新访问的key放到表头,装不下就踢出表尾
  • 怎么实现呢?dict+OrderedDict
    • 这种缓存置换策略一般包含三个方法即可实现:init()get()put()
    from collections import OrderedDict
    # OrderedDict的特点是保存了插入时的顺序
    class LRUCache:
    	def __init__(self,capacity=128):
    		self.capacity = capacity	# 限制容量
    		self.order = OrderedDict()
    	def get(self, k):
    		if k in self.order:
    			val = self.order[k]
    			self.order.move_to_end(k)	# 内置方法,kv移到尾部,改变顺序
    			# 注意:我们视字典尾部为队列表头,即踢出元素时从字典头部
    			return val
    		else:
    			return -1
    	def put(self, k, v):
    		if k in self.order:
    			del self.order[k]
    			self.order[k] = v	# 更新kv,move_to_end()也可以的啦,但关键在于v可能不一样,
    		else:
    			if(self.capacity <= len(self.order)):	# 满了
    				del self.order.popitem(last=False)	# 踢出字典头部(最先进来的)
    			self.order[k] = v	# 插到尾部(表头)
    
  • 使用单元测试看看我们的算法实现对不对吧!

算法常考点

  • 重点:排序+查找(可以看我的文章:重学算法C++版)
  • 要求:独立手写,分析时间复杂度
  • 这里先实现:链表、队列、栈、二叉树、堆
    • 使用内置结构实现高级数据结构
    • LeetCode刷题/《剑指offer》上的题
  • 这里需要你了解常见的数据结构概念,比如什么是单链表、循环链表等
单链表反转
  • 打开LeetCode

    • 这个题目难度更高一点,指定区间的翻转
      py2
    • 链接什么时候切断,什么时候补上去,先后顺序一定要想清楚
    • 上面的思路实现比较复杂,可以每遍历到一个节点,让这个新节点来到反转部分的起始位置,遍历一遍即可:
      py3
    • pre始终指向翻转位置的起点(起始指向left-1)
    • cur指向当前节点(起始指向left)
    • next始终指向cur的下一个节点
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Roy_Allen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值