无论何种编程语言算法和代码逻辑是相同的,不同的只是语法,本文计划收集面试中常会提到的算法或者算法的变种。
求最大质数 求某个数字范围内的最大质数。关于质数的介绍我们在学校数学课上都已经学过了,而且在rsa非对称加密数学计算原理剖析 文章中也详细介绍了什么是质数,因数、互质数之类的。
先理下逻辑,肯定是要一个嵌套循环,外循环负责取出每个数字,内循环依次使用外循环取出的数字去跟比这个数字小的每个数去取余,如果余数为0,则证明这个数可以被其他数整除,可以跳过本次外循环了,继续下个数字进行一次取余。
可能文字看起来让人更懵了,那我们拿个小的数字做下演示就明白了了。
python正向查质数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 arr = [] num = 4 for i in range (2 , num+1 ): for j in range (2 , i): if i%j == 0 : break else : arr.append(i) print (arr)print (max (arr))
上面的方式走了两个步骤,先取出4以内的所有质数;在所有质数列表中获取最大值。
python反向查质数 上面的代码我们是否可以优化一下?我们发现正向的话需要取出所有的质数列表,如果我们反向呢?那第一个质数不就是最大的质数了吗?上面的代码我们只需要改变一小部分既可实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 num = 4 for i in range (num, 1 , -1 ): for j in range (2 , i): if i%j == 0 : break else : print (i) break
php反向查质数 不是所有的语言都可以使用for else的,php就是,我遇到的刚好也是php,正向相对复杂,我这里直接用反向了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $num = 4 for ($i =$num , $i >1 , $i --){ $status = true ; for ($j =1 , $j <i, $j ++){ if ($i %$j == 0 ){ $status = false ; break ; } } if ($status ){ echo "当前最大质数为:" .$i ; break ; } }
可变类型参数 python不可变类型有Number(数字)、String(字符串)、Tuple(元组);可变类型有Set(集合)、List(列表)、Dictionary(字典)。
不可变数据类型 当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称不可变数据类型。
我们就拿string字符串为例,x在修改后,它的内存地址也变了,所以字符串就是不可变的数据类型
1 2 3 4 5 6 7 8 9 10 x = 3 print ("x的原始内存地址:" , id (x))print ("数字3的内存地址:" , id (x))x = 8 print ("x修改后的内存地址:" , id (x))x的原始内存地址: 4475747664 数字3 的内存地址: 4475747664 x修改后的内存地址: 4475747824
可变数据类型 当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类型,就称可变数据类型。
我们拿列表为例,可以看到不管是我们增加还是修改它,它的内存地址没有发生改变,所以列表就是可变的数据类型
1 2 3 4 5 6 7 8 9 lis = [3 , 4 , 6 ] print ("lis的原始内存地址:" , id (lis))lis.append(8 ) lis[1 ] = 9 print ("lis修改后的内存地址:" , id (lis))lis的原始内存地址: 4397477056 lis修改后的内存地址: 4397477056
面试题 列表作为函数参数,写出代码最后的打印结果。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def func (a, b=[] ): b.append(a) return b a = func(3 ) b = func(4 , []) c = func(6 ) print (a)print (b)print (c)[3 , 6 ] [4 ] [3 , 6 ]
结果你想到了吗?我们在函数内部打印下b修改前后的内存id来看下就明白了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def func (a, b=[] ): print ("修改为%s前b的内存地址为:%s" % (a, id (b))) b.append(a) print ("修改为%s后b的内存地址为:%s" % (a, id (b))) return b a = func(3 ) b = func(4 , []) c = func(6 ) print (a, id (a))print (b, id (b))print (c, id (c))修改为3 前b的内存地址为:4370013440 修改为3 后b的内存地址为:4370013440 修改为4 前b的内存地址为:4370019968 修改为4 后b的内存地址为:4370019968 修改为6 前b的内存地址为:4370013440 修改为6 后b的内存地址为:4370013440 [3 , 6 ] 4370013440 [4 ] 4370019968 [3 , 6 ] 4370013440
在第一次函数被调用a = func(3),内存里已经为b开辟了一块内存空间,所有后面b的值如果有更改,它的内存地址一样不会改变的;而b = func(4, [])相当于一个新数组,开辟了一个新的内存空间,所以它不受前后被调用函数的影响,如果这行代码还是不理解的话我们可以更换下代码就明白了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def func (a, b=[] ): print ("修改为%s前b的内存地址为:%s" % (a, id (b))) b.append(a) print ("修改为%s后b的内存地址为:%s" % (a, id (b))) return b l = ['x' , 'y' , 'z' ] print ("修改前l的内存地址为:%s" % id (l))a = func(3 ) b = func(4 , l) l.append('s' ) c = func(6 ) print ("修改后l的内存地址为:%s" % id (l))print (a, id (a))print (b, id (b))print (c, id (c))修改前l的内存地址为:4404205120 修改为3 前b的内存地址为:4404198592 修改为3 后b的内存地址为:4404198592 修改为4 前b的内存地址为:4404205120 修改为4 后b的内存地址为:4404205120 修改为6 前b的内存地址为:4404198592 修改为6 后b的内存地址为:4404198592 修改后l的内存地址为:4404205120 [3 , 6 ] 4404198592 ['x' , 'y' , 'z' , 4 , 's' ] 4404205120 [3 , 6 ] 4404198592
我们这次传入了数组l,不适用空数组了,而且我们是在l被赋值给函数后做的修改,结果依然影响了函数内部的返回结果。打印结果中也可以看出,l的内存地址为函数内部b的内存地址一致。
同理如果换成是字典呢?都是可变类型的,当然执行的逻辑也是相同的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def func (a, b={} ): print ("开始:" , id (b)) b[a] = a print ("结束:" , id (b)) return b a = func(3 ) b = func(4 , {}) c = func(6 ) print (a, b, c)开始: 4481035584 结束: 4481035584 开始: 4481035648 结束: 4481035648 开始: 4481035584 结束: 4481035584 {3 : 3 , 6 : 6 } {4 : 4 } {3 : 3 , 6 : 6 }
类成员方法中的参数添加至类成员属性列表中 其结果跟函数也是类似的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Obj : items = [] def set_items (self, option ): self.items.append(option) o1 = Obj() o2 = Obj() o1.set_items(666 ) o1.set_items('Tony' ) o2.set_items('你好世界' ) print (o1.items, o2.items, Obj.items)[666 , 'Tony' , '你好世界' ] [666 , 'Tony' , '你好世界' ] [666 , 'Tony' , '你好世界' ]
求列表中只出现过一次的元素 从[2, 3, 4, 5, 2, 3, 5, 6, 7]取出只出现过一次的元素
使用count 使用列表方法count直接统计
直接for循环 1 2 3 4 5 6 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = [] for i in lis: if lis.count(i) == 1 : res.append(i)
列表推导式 1 2 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = [i for i in lis if lis.count(i) == 1 ]
使用filter filter匿名函数 1 2 3 4 5 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = filter (lambda x: lis.count(x) == 1 , lis) res = list (res)
filter回调函数 1 2 3 4 5 6 7 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] def func (x ): return lis.count(x)==1 res = filter (func, lis) res = list (res)
使用列表(错误演示) 其实上述的count为内置函数,大多数情况下其实面试官是不让你用的,就是考你的算法,结果你直接用内置好的算法解决了,就考察不出来你的水平了。
1 2 3 4 5 6 7 8 9 10 11 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = [] for i in lis: 如果该元素已经在结果列表中,证明出现的次数不止一次了,直接删除;否则添加该元素到结果列表中 if i in res: res.remove(i) else : res.append(i) print (res)
运行也没有出问题,但是自己检查下代码你会发现疑问,代码中执行过程为遇见结果中包含的元素了,证明是第二次遇到,好我把它删了,如果这个元素重复了3次或者说是奇数次呢?第二次删除后,第三次又循环到该元素,好嘛结果列表中没有该元素了,证明该元素也是第一次出现,这合理吗?
使用扩展包 collections.Counter的作用为计算出可迭代对象中每个元素出现的次数
1 2 3 4 5 6 7 8 9 10 11 12 import collectionslis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = [] dic = collections.Counter(lis) for key, val in dic.items(): if val < 2 : res.append(key) print (res)
使用字典 1 2 3 4 5 6 7 8 9 10 11 12 13 14 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = [] dic = dict .fromkeys(lis, 0 ) for i in lis: dic[i] += 1 for i in dic: if dic[i] == 1 : res.append(i) print (res)
以上代码我们想想还有优化的地方吗?当然是有的,循环列表的时候,当字典中当前元素对应的值大于1的时候,我们直接删除字典中的这一项,后面对于循环字典不是效率更好了吗?优化后的代码看似代码更多了,但是效率却更高了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = [] dic = dict .fromkeys(lis, 0 ) for i in lis: if dic[i] == 1 : del dic[i] else : dic[i] += 1 for i in dic: if dic[i] == 1 : res.append(i) print (res)
高级进阶(异或,位运算) 这个算法只针对于列表中只有一个只出现一次的元素,多个只出现1次的元素则无效了,另外重复元素的重复次数也只能是2次或者偶数次
相同的数字异或的结果是0:2^2=0;和0异或是本身:0^2=2。
我们来看下关于位移或运算符官方的解释:
按位异或运算符:当两对应的二进位相异时,结果为1 ;相同则为0;
其实从概念中就可以理解了,相同的为0,相异为1,如果列表是[1, 2, 1, 4, 6, 4, 6],那么1相同抵消为0了,4抵消为0,6抵消为1,就只剩下2了
1 2 3 4 5 6 alist = [1 , 2 , 1 , 4 , 6 , 4 , 6 ] a = 0 for i in alist: a ^= i print ("a:" , a)
我们拿2进制演示一个简单的数组[2, 3, 3, 4, 2]
为了便于计算,我们将上述数组都转为3位数的2进制
转换后依次为:[010, 011, 011, 100, 010]我们开始循环计算
第一轮计算:
相同为0, 相异为1
010
011
结果为:001
第二轮计算 :
001
011
结果为:010
第三轮计算:
010
100
结果为:110
第四轮计算:
110
010
结果为:100
转为10进制,即为4,所以只出现奇数次的为4
求列表中的偶数元素 从[2, 3, 4, 5, 2, 3, 5, 6, 7]取出偶数元素
直接for循环 1 2 3 4 5 6 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = [] for i in lis: if i%2 ==0 : res.append(i)
列表推导式 本人之前面试直接用的for循环,面试官让用推导式写,本人刚开始写的是[i for i in lis : i%2==0],条件和循环之间用了冒号。
1 2 3 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = [i for i in lis if i%2 ==0 ]
使用filter filter匿名函数 1 2 3 4 5 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] res = filter (lambda x: lis.count(x) == 1 , lis) res = list (res)
filter回调函数 其实面试的时候很多都是面试的基础的东西,在上述面试官让用推导式写好后又让用filter,本人基础不太上心,大概记得大部分内置函数都是调用了一个回调函数。
1 2 3 4 5 6 7 lis = [2 , 3 , 4 , 5 , 2 , 3 , 5 , 6 , 7 ] def func (x ): return lis.count(x)==1 res = filter (func, lis) res = list (res)
本人当时直接filter(func(x), lis)了, 好低级的错误,带括号就是执行了啊,长点心吧。