csdn最全python自动化测试面试题详解

前言#

随着行业的发展,编程能力逐渐成为软件测试从业人员的一项基本能力。因此在笔试和面试中常常会有一定量的编码题,主要考察以下几点。

  • 基本编码能力及思维逻辑
  • 基本数据结构(顺序表、链表、队列、栈、二叉树)
  • 基本算法(排序、查找、递归)及时间复杂度

除基本算法之外,笔试面试中经常会考察以下三种思想:

  • 哈希
  • 递归
  • 分治

目录

前言#

哈希#

 列表去重

使用集合(结果为升序) 

使用字典

使用排序

使用列表生成式

lambda + reduce(大才小用)

while

列表分组

海量数据top K

1. 问题描述

2. 当前解决方案

3. 解决方案

 两数之和

递归问题

阶乘#

斐波那切数列 

跳台阶

题目描述

变态跳台阶

题目描述

快速排序#

二分查找#

二叉树遍历#

二叉树最大深度#

相等二叉树判断#

平衡二叉树判断#

其他#

字符串统计#

统计重复最多的n个字符#

字符串反转#

判断括号是否闭合#

合并两个有序列表,并保持有序#

两个队列实现一个栈#


哈希#

哈希即Python中的映射类型,字典和集合,键值唯一,查找效率高,序列(列表、元祖、字符串)的元素查找时间复杂度是O(n),而字典和集合的查找只需要O(1)。
因此哈希在列表问题中主要有两种作用:

  1. 去重
  2. 优化查找效率

 列表去重

   待去重列表
   lt1 = [1,3,2,3,4,5,3,5]

使用集合(结果为升序) 

lt2 = list(set(lt1))

使用字典

lt2 = list({}.fromkeys(lt1).keys())

使用排序

lt2 = sorted(set(lt1),key=lt1.index)

使用列表生成式

lt2 = []
[lt2.append(i) for i in lt1 if not i in lt2]

即:

lt2 = []
for i in lt1:
    if i not in lt2:
        lt2.append(i)

lambda + reduce(大才小用)

func = lambda x,y:x if y in x else x + [y]  
lt2 = reduce(func, [[], ] + lt1)

while

for x in lt1:
    while lt1.count(x)>1:
        del lt1[lt1.index(x)]

列表分组

问题:最近遇到一个小问题,需要对列表中的元素分组,保证每组元素的和尽可能平衡,最后返回每组的值和所对应的下标,这里对处理办法进行记录,方便以后查看。  

  解决思路

    step1: 对列表进行排序,新建输出的空列表out_list[N];

    step2: 根据需要分组的个数NN,每次遍历NN个元素;

    step3: 循环执行step2,前后两次交替分配;

    step4: 处理最后未分配的元素,依次分配给out_list。

  代码

def listGroupBySum(arr, N):
    sorted_arr = sorted(enumerate(arr), key=lambda x:x[1])  # index: x[0]  value: x[1]
    out_list = [[] for i in range(N)]
    
    for i in range(len(arr) // N):
        for j in range(N):
            if i % 2 == 0:
                out_list[j].append(sorted_arr[i*N+j])
            else:
                out_list[N-j-1].append(sorted_arr[i*N+j])
    
    # the remain items of arr
    for i in range(len(arr) // N * N, len(arr)):
        out_list[i%N].append(sorted_arr[i])
        
    return out_list

  例子:

csdn最全python自动化测试面试题详解_第1张图片

海量数据top K

1. 问题描述

在大规模数据处理中,常遇到的一类问题是,在海量数据中找出出现频率最高的前K个数,或者从海量数据中找出最大的前K个数,这类问题通常称为“top K”问题,如:在搜索引擎中,统计搜索最热门的10个查询词;在歌曲库中统计下载率最高的前10首歌等等。

2. 当前解决方案

针对top k类问题,通常比较好的方案是【分治+trie树/hash+小顶堆】,即先将数据集按照hash方法分解成多个小数据集,然后使用trie树或者hash统计每个小数据集中的query词频,之后用小顶堆求出每个数据集中出频率最高的前K个数,最后在所有top K中求出最终的top K。

实际上,最优的解决方案应该是最符合实际设计需求的方案,在实际应用中,可能有足够大的内存,那么直接将数据扔到内存中一次性处理即可,也可能机器有多个核,这样可以采用多线程处理整个数据集。

本文针对不同的应用场景,介绍了适合相应应用场景的解决方案。

3. 解决方案

3.1 单机+单核+足够大内存

设每个查询词平均占8Byte,则10亿个查询词所需的内存大约是10^9*8=8G内存。如果你有这么大的内存,直接在内存中对查询词进行排序,顺序遍历找出10个出现频率最大的10个即可。这种方法简单快速,更加实用。当然,也可以先用HashMap求出每个词出现的频率,然后求出出现频率最大的10个词。

3.2 单机+多核+足够大内存

这时可以直接在内存中实用hash方法将数据划分成n个partition,每个partition交给一个线程处理,线程的处理逻辑是同3.1节类似,最后一个线程将结果归并。

该方法存在一个瓶颈会明显影响效率,即数据倾斜,每个线程的处理速度可能不同,快的线程需要等待慢的线程,最终的处理速度取决于慢的线程。解决方法是,将数据划分成c*n个partition(c>1),每个线程处理完当前partition后主动取下一个partition继续处理,直到所有数据处理完毕,最后由一个线程进行归并。

3.3 单机+单核+受限内存

这种情况下,需要将原数据文件切割成一个一个小文件,如,采用hash(x)%M,将原文件中的数据切割成M小文件,如果小文件仍大于内存大小,继续采用hash的方法对数据文件进行切割,直到每个小文件小于内存大小,这样,每个文件可放到内存中处理。采用3.1节的方法依次处理每个小文件。

3.4 多机+受限内存

这种情况下,为了合理利用多台机器的资源,可将数据分发到多台机器上,每台机器采用3.3节中的策略解决本地的数据。可采用hash+socket方法进行数据分发。

从实际应用的角度考虑,3.1~3.4节的方案并不可行,因为在大规模数据处理环境下,作业效率并不是首要考虑的问题,算法的扩展性和容错性才是首要考虑的。算法应该具有良好的扩展性,以便数据量进一步加大(随着业务的发展,数据量加大是必然的)时,在不修改算法框架的前提下,可达到近似的线性比;算法应该具有容错性,即当前某个文件处理失败后,能自动将其交给另外一个线程继续处理,而不是从头开始处理。

Top k问题很适合采用MapReduce框架解决,用户只需编写一个map函数和两个reduce 函数,然后提交到Hadoop(采用mapchain和reducechain)上即可解决该问题。对于map函数,采用hash算法,将hash值相同的数据交给同一个reduce task;对于第一个reduce函数,采用HashMap统计出每个词出现的频率,对于第二个reduce 函数,统计所有reduce task输出数据中的top k即可。

 两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

1

2

3

4

5

6

7

8

class Solution:

    def twoSum(self, nums, target):

         if len(num)<2:

                print None#如果数字的长度小于2,直接返回None

         for in range(0,len(num)-1):

            for in range(i+1,len(num)):

                if num[i]+num[j]=target:

                    return i+j

递归问题

递归是一种循环调用自身的函数。可以用于解决以下高频问题:

  • 阶乘
  • 斐波那切数列
  • 跳台阶、变态跳台阶
  • 快速排序
  • 二分查找
  • 二叉树深度遍历(前序、中序、后序)
  • 求二叉树深度
  • 平衡二叉树判断
  • 判断两颗树是否相同

递归是一种分层推导解决问题的方法,是一种非常重要的解决问题的思想。递归可快速将问题层级化,简单化,只需要考虑出口和每层的推导即可。
如阶乘,要想求n!,只需要知道前一个数的阶乘(n-1)!,然后乘以n即可,因此问题可以转为求上一个数的阶乘,依次向前,直到第一个数。
举个通俗的例子:
A欠你10万,但是他没那么多钱,B欠A 8万,C欠B 7万 C现在有钱。因此你要逐层找到C,一层一层还钱,最后你才能拿到属于你的10万。

编写递归函数有两个要点:

  1. 出口条件,可以不止一个
  2. 推导方法(已知上一个结果怎么推导当前结果)

阶乘#

求n的阶乘

  • 出口:n = 1 时,返回1
  • 推导:(n-1)层的结果 * n

代码如下:

 
  

Copy

def factorial(n): if n == 1: # 出口 return 1 return factorial(n-1) * n # 自我调用求上一个结果,然后推导本层结果

也可以简写为 factorial = lambda n: 1 if n==1 else factorial(n-1) * n

斐波那切数列 

问题描述

斐波那切数列0,1,1,2,3,5,8,13,21,34,55……从第三项起,每一项都是紧挨着的前两项的和。写出计算斐波那切数列的任意一个数据项递归程序。

输入格式

输入所求的项数。

输出格式

    输出数据项的值。

输入样例fbi.in

    10

输出样例fbi.out

    34

#include
#include
#include
#include
#include
using namespace std;
int dg(int m)
{
    if(m==1)return 0;
    if(m==2)return 1;
    else return dg(m-1)+dg(m-2);
}
int main()
{
    int m;
    cin>>m;
    cout< 
  

跳台阶

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

 1 public class Main08 {
 2 
 3     /*
 4      * 一只青蛙一次可以跳上1级台阶,也可以跳上2级。
 5      * 求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
 6      */
 7     
 8     public static void main(String[] args) {
 9         // TODO Auto-generated method stub
10         int num = Main08.JumpFloor(4);
11         System.out.println(num);
12     }
13     
14     public static int JumpFloor(int target) {
15         if(target == 1) {
16             return 1;
17         }
18         if(target == 2) {
19             return 2;
20         }
21         
22         return JumpFloor(target-1) + JumpFloor(target-2);
23     }
24 
25 }

变态跳台阶

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

 1 /*
 2  * 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求
 3  * 该青蛙跳上一个n级的台阶总共有多少种跳法。
 4  */
 5 
 6 import java.util.Scanner;
 7 
 8 public class Main09 {
 9 
10     public static void main(String[] args) {
11         // TODO Auto-generated method stub
12         Scanner sc = new Scanner(System.in);
13         int target = sc.nextInt();
14         int times = Main09.JumpFloorII(target);
15         System.out.println(times);
16     }
17     
18     public static int JumpFloorII(int target) {
19         if(target == 0) {
20             return 0;
21         }
22         if(target == 1) {
23             return 1;
24         }
25 
26         return 2*JumpFloorII(target-1);
27     }
28 
29 }

这两道题目都是斐波那契数列的扩展。

跳台阶:每一次只能跳1阶或者2阶;

所以F(1) = 1;

F(2) = F(2-1) + F(2-2);  ----------→ F(2-1) :青蛙第一次跳了一个台阶后的情况。 举例排列的话就是 1 x, x 为 1。 F(2-2):青蛙第一次跳了两个台阶的情况。举例排列的话就是 2 。

F(3) = F(3-1) + F(3-2); ----------→ F(3-1):青蛙第一次跳了一个台阶后的情况。F(3-2):青蛙第一次跳了两个台阶的情况。  。。。。。以此类推。

F(n) = F(n-1) + F(n-2); 

变态跳台阶:每一次只能跳1阶或者2阶或者3阶.......n阶。

F(1) = 1;

F(2) = F(2-1) + F(2-2);  ----------→ F(2-1) :青蛙第一次跳了一个台阶后的情况。 举例排列的话就是 1 x, x 为 1。 F(2-2):青蛙第一次跳了两个台阶的情况。举例排列的话就是 2 。

F(3) = F(3-1) + F(3-2) + F(3-3); ----------→ F(3-1):青蛙第一次跳了一个台阶后的情况。F(3-2):青蛙第一次跳了两个台阶的情况。  。。。。。以此类推。

............

F(n-1) = F(n-2) + F(n-3) + ......+ F(1) + F(0); 

F(n) = F(n-1) + F(n-2) + ......+ F(1) + F(0);

根据F(n-1)的表达式和F(n) 的表达式 我们可以轻松的推出:F(n) = 2 * F(n-1);   这样就得到了 我们的迭代表达式。

快速排序#

快速排序的是想是选一个基准数(如第一个数),将大于该数和小于该数的分成两块,然后在每一块中重复执行此操作,直到该块中只有一个数,即为有序。

  • 出口:列表长度为1(<2)时,返回列表
  • 选择一个数,(将小于该数的序列)排序结果 + 基准数 + (大于该数的序列)排序结果
def quick_sort(l): 
    if  len(l) < 2:
         return l
    target = l[0]  # 以第一个数为基准数
    low_part, eq_part, high_part = [], [target], []
    for i in l[1:]:
        if i < target:
            low_part.append(i)
        elif i == target:
            eq_part.append(i)
        else:
            high_part.append(i)
    return quick_sort(low_part) + eq_part + quick_sort(high_part)

二分查找#

二分查找需要序列首先有序。思想是先用序列中间数和目标值对比,如果目标值小,则从前半部分(小于中间数)重复此查找,否则从后半部分重复此查找。

  • 出口1:中间数和目标数相同,返回中间数下标
  • 出口2:列表为空,返回未找到
  • 推导:
def bin_search(l, n): 
    if not l:
        return None
    mid = len(l) // 2
    if l[mid] == n:
        return mid
    if l[mid] > n:
       return bin_search(l[:mid])
    return  bin_search(l[mid+1:])

二叉树遍历#

二叉树是非常常考的一种数据结构。其基本结构就是一个包含数据和左右节点的一种结构,使用Python类描述如下:

 
  
class Node(object):
    def __init__(self, data, left=None, right=None):
        self.data = data
        self.left = left
        self.right = right

二叉树的遍历分为分层遍历(广度优先)和深度遍历(深度优先)两种,其中深度遍历又分为前序、中序、后序三种。

分层遍历由于每次处理多个节点,使用循环解决更加方便一点(也可以是使用递归解决)。
分层遍历代码如下:

class Node(object):
    def __init__(self, data, left=None, right=None):
        self.data = data
        self.left = left
        self.right = right

深度遍历

  • 出口:节点为None
  • 推导:
    • 前序:打印当前节点-》遍历左子树 -》遍历右子树
    • 中序:遍历左子树 -》打印当前节点-》遍历右子树
    • 后序:遍历左子树 -》遍历右子树-》打印当前节点

以前序为例:

def deep(root):
    if root is none:
        return
    [print(root.data), deep(root.left), deep(root.right)]

二叉树最大深度#

二叉树最大深度即其左子树深度和右子树深度中最大的一个加上1(当前节点)。由于二叉树的每一个左右节点都是一个二叉树,这种层层嵌套的结构非常适合使用递归求解。

  • 出口:节点为空,深度返回0
  • 推导:左子树深度和右子树深度中最大的一个 + 1
def max_depth(root):
    if not root:
        return 0
    return max([max_depth(root.left), max_depth(root.right)]) + 1

相等二叉树判断#

相等二叉树是只,一个二叉树,节点数据相同,左右子树也完全相同。由于左右子树也是一个二叉树,因此也可以使用递归求解。

  • 出口:最后的节点都为None时,两个相等,返回True
  • 推导:判断两个节点数据是否相等,左子树是否相等(递归),右子树是否相等(递归)
def is_same_tree(p, q):
    if p is None and q is None:
        return True
    elif p and q:
        return p.data == q.data and is_same_tree(p.left, q.left) and is_same_tree(p.right, q.right)

平衡二叉树判断#

平衡二叉树是指,一个二叉树的左右子树的高度差不超过1。平衡二叉树的左右子树也应该是平衡二叉树,因此这也是一个递归问题。

  • 出口:两个节点都为None时,返回True(平衡)
  • 判断左子树和右子树深度的差<=1,并且左右子树都是平衡二叉树(递归)

注:这里需要使用以上求二叉树深度的方法

def max_depth(root):
    if not root:
        return 0
    return max([max_depth(root.left), max_depth(root.right)]) + 1

def is_balance_tree(root):
    if root is None:
        return True
    return abs(max_depth(root.left)-max_depth(root.right))<=1 and is_balance_tree(root.left) and is_balance_tree(root.right)

其他#

字符串统计#

str1 = 'abcdaacddceea'
set1 = set(str1)
result = [(char, str1.count(char)) for char in set1]
print(result)

统计重复最多的n个字符#

from collections import Counter
c = Counter('abcdaacddceea')
print(c.items())
print(c.most_common(3))

字符串反转#

  • 简单字符串反转
    Python中字符串反转方式非常多,而且比较高效,可以使用反向切片或者reverse实现。
'abcefg'[::-1]
''.join(reversed('abcdefg'))
  • 包含数字字母的字符串,仅反转字母
    可以通过遍历判断,如果是字母则取其对应反转索引位置的字母,如果是数字则取当前数字。
a = 'abc123efg'
l = len(a)
r = []
for i,c in enumerate(a):
    r.append(c) if c.isdigit() else r.append(a[l-i-1])    
print(''.join(r))

判断括号是否闭合#

这是栈使用的一个经典示例,思路为,遇到正括号则入栈,遇到反括号则和栈顶判断,如果匹配则匹配的正括号出栈(完成一对匹配),否则打印不匹配,break退出。

 
  
text = "({[({{abc}})][{1}]})2([]){({[]})}[]"

def is_closed(text)
    stack = []  # 使用list模拟栈, stack.append()入栈, stack.pop()出栈并获取栈顶元素
    brackets = {')':'(',']':'[','}':'{'}  # 使用字典存储括号的对应关系, 使用反括号作key方便查询对应的括号
    for char in text:
        if char in brackets.values():   # 如果是正括号,入栈
            stack.append(char)
        elif char in brackets.keys():  # 如果是反括号
            if brackets[char] != stack.pop():  # 如果不匹配弹出的栈顶元素
                return False
    return True

print(is_closed(text))

合并两个有序列表,并保持有序#

常见的解法有两种:

  • 连接 + 排序,时间复杂度度为O((m+n)log2(m+n))
  • 两个队列根据大小依次弹出,时间复杂度度约为O(m+n)

依次出队列的逻辑为:

  • 队列1为空,队列2不为空,从队列2弹出一个数据
  • 队列2为空,队列1不为空,从队列1弹出一个数据
  • 两个都不为空,判断两个对队列顶端哪个小,从哪个列表弹出一个数据

以下为使用Python列表模拟两个队列依次弹出的示例。
由于Python列表尾部弹出list.pop()的的操作效率O(1),比首部弹出list.pop(0)的操作效率O(n)更高,因此我们先按从大到小排序,最后在执行一次反转。

list1 = [1,5,7,9]
list2 = [2,3,4,5, 6,8,10,12,14]
result = []
for i in range(len(list1) + len(list2)):
    if list1 and not list2:
        result.append(list1.pop())
    elif list2 and not list1:
        result.append(list2.pop())
    else:
        result.append(list1.pop()) if list1[-1] > list2[-1] else result.append(list2.pop())  # 弹出顶端大的数
result.reverse()  # 执行反转
print(result)

两个队列实现一个栈#

队列是先入先出,栈是先入后出。
使用两个队列实现栈的方式有很多种,主要分为优化入栈和优化出栈两种,以下为优化入栈的一种实现方法。

  • 入栈时直接存入队列q1
  • 出栈时,将q1中元素依次放入q2, 直到最后一个元素,弹出元素,然后将q2中元素重新依次放回q1

实现代码如下:

import queue

class Stack(object):
    def __init__(self):
        self.q1 = queue.Queue()
        self.q2 = queue.Queue()

    def push(self, value):
        self.q1.put(value)

    def pop(self):
        while self.q1.qsize() > 1:
            self.q2.put(self.q1.get())
        value = self.q1.get()
        while not self.q2.empty():
            self.q1.put(self.q2.get())
        return value

测试代码:

 
  
s = Stack()
[s.push(i) for i in [1,2,3,4,5,6,7]]
print(s.pop())
print(s.pop())
print(s.pop())
print(s.pop())

打印结果为:

7
6
5
4

你可能感兴趣的:(面试,职场和发展,经验分享,程序人生,测试工具)