常见算法笔试题
算法与数据结构是面试考察的重中之重,也是日后刷题时需要着重训练的部分。
简单的总结一下,大约有这些内容:
算法 - Algorithms
1、排序算法:快速排序、归并排序、计数排序
2、搜索算法:回溯、递归、剪枝技巧
3、图论:最短路、最小生成树、网络流建模
4、动态规划:背包问题、最长子序列、计数问题
5、基础技巧:分治、倍增、二分、贪心
1、数组与链表:单/双向链表、跳舞链
2、栈与对列
3、树与图:最近公共祖先、并查集
4、哈希表
5、堆:大/小根堆、可并堆
6、字符串:字典树、后缀树
递归与迭代的区别
递归(recursion):递归常被用来描述以自相似方法重复事物的过程,在数学和计算机科学中,指的是在函数定义中使用函数自身的方法。(A调用A)
迭代(iteration):重复反馈过程的活动,每一次迭代的结果会作为下一次迭代的初始值。(A重复调用B)
递归是一个树结构,从字面可以其理解为重复“递推”和“回归”的过程,当“递推”到达底部时就会开始“回归”,其过程相当于树的深度优先遍历。
迭代是一个环结构,从初始状态开始,每次迭代都遍历这个环,并更新状态,多次迭代直到到达结束状态。
# 理论上递归和迭代时间复杂度方面是一样的,但实际应用中(函数调用和函数调用堆栈的开销)递归比迭代效率要低。
链接:https://www.jianshu.com/p/32bcc45efd32
来源:简书
算法的时间复杂度和空间复杂度
-
时间复杂度和空间复杂度是用来评价算法效率高低的2个标准。
-
时间复杂度:就是说执行算法需要消耗的时间长短,越快越好。比如你在电脑上打开计算器,如果一个普通的运算要消耗1分钟时间,那谁还会用它呢,还不如自己口算呢。
-
空间复杂度:就是说执行当前算法需要消耗的存储空间大小,也是越少越好。本来计算机的存储资源就是有限的,如果你的算法总是需要耗费很大的存储空间,这样也会给机器带来很大的负担。
时间复杂度的计算
表示方法
我们一般用“大O符号表示法”来表示时间复杂度:T(n) = O(f(n)) n是影响复杂度变化的因子,f(n)是复杂度具体的算法。
常见的时间复杂度量级
-
常数阶O(1)
-
线性阶O(n)
-
对数阶O(logN)
-
线性对数阶O(nlogN)
-
平方阶O(n²)
-
立方阶O(n³)
-
K次方阶O(n^k)
-
指数阶(2^n)
常见的时间复杂度(按效率排序) O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)
接下来再看一下不同的复杂度所对应的算法类型。
常数阶O(1)
int a = 1;
int b = 2;
int c = 3;
线性阶O(n)
for(i = 1; i <= n; i++) {
j = i;
j++;
}
对数阶O(logN)
int i = 1;
while(i < n) {
i = i * 2;
}
线性对数阶O(nlogN)
for(m = 1; m < n; m++) {
i = 1;
while(i < n) {
i = i * 2;
}
}
平方阶O(n²)
for(x = 1; i <= n; x++){
for(i = 1; i <= n; i++) {
j = i;
j++;
}
}
空间复杂度计算
空间复杂度 O(1)
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)。
int i = 1; int j = 2; ++i; j++; int m = i + j;
代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)。
空间复杂度 O(n)
int[] m = new int[n] for(i = 1; i <= n; ++i) { j = i; j++; }
这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,后面虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)。
十大经典排序算法
# 冒泡排序 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 针对所有的元素重复以上的步骤,除了最后一个。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 import sys sys.setrecursionlimit(1000000) ## 冒泡排序 (******) ### 时间复杂度:O(n^2) def Bubble_sort(li): for i in range(len(li)-1): for j in range(len(li)-1-i): if li[j] > li[j+1]: li[j], li[j+1] = li[j+1], li[j] ## 选择排序 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。 重复第二步,直到所有元素均排序完毕。 #### 时间复杂度:O(n^2) def select_sort(li): for i in range(len(li)): minLoc = i ###i = 0 for j in range(i+1, len(li)): if li[j] < li[minLoc]: li[j], li[minLoc] = li[minLoc], li[j] ##### 插入排序(打扑克牌) 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。) #### 时间复杂度: O(n^2) def insert_sort(li): for i in range(1, len(li)): tmp = li[i] j = i - 1 while j >=0 and li[j] > tmp: li[j+1] = li[j] j = j - 1 li[j+1] = tmp def partition(li, left, right): tmp = li[left] while left < right: while left < right and li[right] >= tmp: right = right - 1 li[left] = li[right] while left < right and li[left] <= tmp: left = left + 1 li[right] = li[left] li[left] = tmp return left ## 快速排序 1、从数列中挑出一个元素,称为 "基准"(pivot); 2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; 3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序; ## 时间复杂度:O(nlogn) def quick_sort(li, left, right): if left < right: mid = partition(li, left, right) quick_sort(li, left, mid-1) quick_sort(li, mid+1, right) # 计算时间复杂度 import time,random li = [random.randint(1,100) for _ in range(100000)] start = time.time() quick_sort(li, 0, len(li)-1) cost = time.time() - start print('quick_sort:%s' % (cost)) import time,random li = [random.randint(1,100) for _ in range(100000)] start = time.time() Bubble_sort(li) cost = time.time() - start print('bubble_sort:%s' % (cost)) import time,random li = [random.randint(1,100) for _ in range(100000)] start = time.time() insert_sort(li) cost = time.time() - start print('insert_sort:%s' % (cost))
算法-力扣(LeetCode)
链表反转
反转一个单链表:
输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 进阶: 你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
# 双向指针迭代 class Solution(object): def reverseList(self, head): """ :type head: ListNode :rtype: ListNode """ # 申请两个节点,pre和 cur,pre指向None pre = None cur = head # 遍历链表,while循环里面的内容其实可以写成一行 # 这里只做演示,就不搞那么骚气的写法了 while cur: # 记录当前节点的下一个节点 tmp = cur.next # 然后将当前节点指向pre cur.next = pre # pre和cur节点都前进一位 pre = cur cur = tmp return pre # 2、递归解法 class Solution(object): def reverseList(self, head): """ :type head: ListNode :rtype: ListNode """ # 递归终止条件是当前为空,或者下一个节点为空 if(head==None or head.next==None): return head # 这里的cur就是最后一个节点 cur = self.reverseList(head.next) # 这里请配合动画演示理解 # 如果链表是 1->2->3->4->5,那么此时的cur就是5 # 而head是4,head的下一个是5,下下一个是空 # 所以head.next.next 就是5->4 head.next.next = head # 防止链表循环,需要将head.next设置为空 head.next = None # 每层递归函数都返回cur,也就是最后一个节点 return cur 来源:力扣(LeetCode)
反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[]
的形式给出。
示例
输入:["h","e","l","l","o"] 输出:["o","l","l","e","h"] 简单实现 class Solution: def reverseString(self, s): s.reverse() # 1、递归实现 class Solution: def reverseString(self, s): def helper(left, right): if left < right: s[left], s[right] = s[right], s[left] helper(left + 1, right - 1) helper(0, len(s) - 1) # 2、一部实现 class Solution: def reverseString(self, s: List[str]) -> None: """ Do not return anything, modify s in-place instead. """ # one step by python s[:] = s[::-1] 来源:力扣(LeetCode)
二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例: 给定二叉树 [3,9,20,null,null,15,7]
3 / \ 9 20 / \ 15 7
方法:递归
直观的方法是通过递归来解决问题。在这里,我们演示了 DFS(深度优先搜索)策略的示例。
class Solution: def maxDepth(self,root): # root子节点 if root is None: return 0 else: left_height = self.maxDepth(root.left) right_height = self.maxDepth(root.right) return max(left_height,right_height)+1 #左右对比 基于子节点+1
整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例:
输入: 123 输出: 321
方法:字符串的反转,记录符号位。 class Solution: def reverse(self, x: int) -> int: flag = -1 if x < 0 else 1 res = flag * int(str(abs(x))[::-1]) return res if (-2**31)<=res<=(2**31-1) else 0 作者:powcai
删除排序中数组中的重复项
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例:
给定 nums = [0,0,1,1,1,2,2,3,3,4], 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 你不需要考虑数组中超出新长度后面的元素。
思路:
用两个指针,指向第一个和第二个元素,如果他们相等,删除第二个元素。指针还指向原来的位置,继续比较。不等的话,两个指针位置都加一。遍历结束即可。
class Solution: def removeDuplicates(self, nums: List[int]) -> int: pre,cur=0,1 # 指针初始指向数 while cur<len(nums): if nums[pre]==nums[cur]: nums.pop(cur) # 取出重复数 else: pre,cur=pre+1,cur+1 # 交换并加一往后 return len(nums)# 数组的长度
存在重复元素
给定一个整数数组,判断是否存在重复元素。
如果任意一值在数组中出现至少两次,函数返回 true
。如果数组中每个元素都不相同,则返回 false
。
示例:
输入: [1,2,3,1] 输出: true 输入: [1,2,3,4] 输出: false
集合判断法:利用Python独有的数据类集合特性。 用nums = list(set(nums))就可以轻松排除了
class Solution(object): def containsDuplicat(self,nums:list[int]) ->bool: if len(self(nums) == len(nums)) return Flase else: return True
只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
示例:
输入: [4,1,2,1,2] 输出: 4
思路:通过数学的解法
先通过set把数据去重,然后把所有的值相加*2去减之前的值,剩下的值就是答案
1、数学 class Solution(object): def singleNumber(self, nums): return 2 * sum(set(nums)) - sum(nums) 2、counter函数 from collections import Counter class Solution: def singleNumber(self, nums: List[int]) -> int: datas = Counter(nums) for each in datas: if datas[each] == 1: return each
两数之和
给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
#思路:两层for循环,取出值进行相加,判断是否等于target 用两个for循环解决,注意,第二个for循环的值得是第一个的值+1 class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: for i in range(len(nums)): for j in range(i+1,len(nums)):# 基于第一次之上 if nums[i] + nums[j] == target: return [i,j]
加一
示例:
输入: [1,2,3] 输出: [1,2,4] 解释: 输入数组表示数字 123。
class Solution(object): def plusOne(self, digits): """ :type digits: List[int] :rtype: List[int] """ digits_str='' for i in range(len(digits)): digits_str+=str(digits[i]) digits_num=int(digits_str)+1 digits_list=list(str(digits_num)) return digits_list
小猿取经-博客
查找list中最大的数
-
思路:遍历整个列表 获得对应的元素,元素与元素之间进行比较,记录最大值
list0 = [12,45,-2,6,-9,78,99,154] 1、max内置函数 res=max(list0) print(res) 2、for遍历比较 max_item = list0[0] for item in range(0,len(list0)): if max_item < list0[item]: max_item=list0[item] print(max_item) 3、for循环 for item in list0: if max_item <item: max_item = item print(max_item)
两个列表中找出相同和不相同的元素
1、set集合去重,取交集 list1 = [1,2,3] list2 = [3,4,5] set1 = set(list1) set2 = set(list2) print(set1&set2) print(set1^set2) 2、列表推导式 list1 = [1,3,65,78] list2 = [1,3,8,58,78,23] c = [x for x in list1 if x in list2] b = [y for y in (list1+list2) if y not in c] print(c) print(b) 3、for循环 list1 = [1,3,5,8,9] list2 = [2,3,5,12,45] list3 = [] list4 = [] for i in list1: for j in list2: if i == j: list3.append(i) for b in (list1+list2): if b not in list3: list4.append(b) print(list3) print(list4)
列表去重
列表去重的几种方式 1. for 循环去重 list1 = [2, 1, 3, 6, 2, 1] temp = [] for i in list1: if not i in temp: temp.append(i) 2. 列表推导式去重 list1 = [2, 1, 3, 6, 2, 1] temp = [] [temp.append(i) for i in list1 if i not in temp] print(temp) 3. set去重 list1 = [2, 1, 3, 6, 2, 1] temp = list(set(list1)) print(temp) set去重保持原来的顺序,参考5,6 4. 使用字典fromkeys()的方法来去重 原理是: 字典的key是不能重复的 list1 = [2, 1, 3, 6, 2, 1] temp = {}.fromkeys(list1) print(temp) print(temp.keys()) 5 . 使用sort + set去重 list1 = [2, 1, 3, 6, 2, 1] list2 = list(set(list1)) list2.sort(key=list1.index) print(list2) 6. 使用sorted+ set函数去重 list1 = [2, 1, 3, 6, 2, 1] temp = sorted(set(list1), key=list1.index) print(temp) 删除列表中的重复项 list1 = [2, 1, 3, 6, 2, 1] temp = [item for item in list1 if list1.count(item) == 1] print(temp) list1 = [2, 1, 3, 6, 2, 1] temp = list(filter(lambda x:list1.count(x) ==1, list1)) print(temp)
9*9乘法表
for row in range(1, 10): for col in range(1, row+1): print('{}*{}={}'.format(col, row, col * row), end='\t') print() # 左下三角九九乘法表 for row in range(1,10): #打印行 for col in range(1,row+1): #打印列 print("{0}*{1}={2:2d}".format(row,col,row*col),end=" ") #这里是用format函数进行格式化输出控制,{2:2d}是给{2}这个位置两倍的空间,对齐乘法表 #同时end是print函数内置方法,设置end=""print就不会进行换行操作 print(" ") #这里是用print的特性,进行换行输出 ———————————————— # author : Eric a = 1 sum = 0 for i in range(1,10): for j in range(1,i+1): sum = i*j print("%dx%d=%d"%(i,j,sum),end=" ") print("") ———————————————— # 一行实现 print('n'.join([' '.join(['%s*%s=%-2s' % (y, x, x*y) for y in range(1, x+1)]) for x in range(1, 10)]))
完数
完数的定义:
什么是因子? 假如整数n除以m,结果是无余数的整数
n % m == 0
,那么我们称m就是n的因子.需要注意的是,唯有被除数,除数,商皆为整数,余数为零时,此关系才成立.
什么是“完数”: 一个数如果恰好等于它的因子之和,这个数就称为”完数”。例如6=1+2+3.
如一个数恰好等于它的因子之和,这个数就称为“完数”。
如:6=1+2+3,请实现1000内的所有完数。
for i in range(2,1001): # 遍历1000以内的所有数,从2 开始 s = i # 把取出的数赋值给另一个变量s,用于与所有因子作差,若果减去所有的因子后结果为0,这个数即为完数。 for j in range(1,i): # 查找因子 if i % j == 0: # 找出因子 s -= j # 与因子作差 if s == 0: # 判断是否是完数 print(i) # 打印完数 # 优化 for i in range(2,1001): k = [] # 用于收集一个数的所有因子 n = -1 # s = i for j in range(1,i): if i % j == 0: n += 1 s -= j k.append(j) # 收集所有因子 if s == 0: print(i,":") # 打印完数 for j in range(n): # 遍历完数的所有因子 print(str(k[j]),end='+ ') # 打印出所有的因子 print(k[n]) # 打印 # 方法2 l = [ ] for n in range (1,10000): for a in range (1,n): if n%a ==0: l.append(a) if sum(l)==n: print (l) print (n) l = [] ————————————————
字符串的排列组合
给你一个字符串,比如‘abc’,请打印出该字符串的所有排列组合:
以‘abc’为例,输出的结果应该是:'abc', 'acb', 'bac', 'bca', 'cab', 'cba'
请用python代码编码实现:
def fun1(s=''): if len(s) <= 1: return [s] else: sl = [] for i in range(len(s)): for j in fun1(s[0:i] + s[i + 1:]): sl.append(s[i] + j) return sl def main(): a = fun1('abc') print(a)
二分法查找
def bin_search_rec(data_set, value, low, high): if low <= high: mid = (low + high) // 2 if data_set[mid] == value: return mid elif data_set[mid] > value: return bin_search_rec(data_set, value, low, mid - 1) else: return bin_search_rec(data_set, value, mid + 1, high) else: return 2、递归实现 def search(list, key): left = 0 # 左边界 right = len(list) - 1 # 右边界 while left <= right: mid = (left + right) // 2 # 取得中间索引 if key > list[mid]: left = mid + 1 elif key < list[mid]: right = mid - 1 else: return mid else: return -1 list = [2, 5, 13, 21, 26, 33, 37] print(search(list, 5)) ———————————————— 原文链接:https://blog.csdn.net/weixin_42068613/java/article/details/83187817 3、 def binary_search(list, item): low = 0 # (以下2行)low和high用于跟踪要在其中查找的列表部分 high = len(list) - 1 n = 0 while low <= high: # 只要范围没有缩小到只包含一个元素 n += 1 mid_1 = (low + high)/2 # 就检查中间的元素 mid = int(mid_1) guess = list[mid] if guess == item: # 找到了元素 return "要猜的数字在列表中的索引号为%d,共猜了%d次"%(mid,n) if guess > item: # 猜的数字大了 high = mid -1 else: # 猜的数字小了 low = mid + 1 return "没有这个数" # 没有指定的元素 my_list = [] for i in range(1, 201): my_list.append(i) print(my_list) print(binary_search(my_list, 100)) print(binary_search(my_list, 111)) #运行结果 [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, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200] 要猜的数字在列表中的索引号为99,共猜了1次 要猜的数字在列表中的索引号为110,共猜了8次 Process finished with exit code 0
两个数组的交集 II
简单地利用数组操作,一次循环就能找出结果。 class Solution: def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]: res = [] for i, num in enumerate(nums1): if num in nums2: res.append(num) nums2.pop(nums2.index(num)) return res 来源:力扣(LeetCode)
合并两个有序的链表
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4 # Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: if not l1: return l2 # 终止条件,直到两个链表都空 if not l2: return l1 if l1.val <= l2.val: # 递归调用 l1.next = self.mergeTwoLists(l1.next,l2) return l1 else: l2.next = self.mergeTwoLists(l1,l2.next) return l2
本文来自博客园,作者:游走De提莫,转载请注明原文链接:https://www.cnblogs.com/Gaimo/articles/13418185.html