一、Apple外包
没问理论,就两个算法题。
1.矩阵顺时针旋转遍历
Given an m x n matrix, return all elements of the matrix in spiral order.
Example 1:
Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [1,2,3,6,9,8,7,4,5]
Example 2:
Input: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
Output: [1,2,3,4,8,12,11,10,9,5,6,7]
Constraints:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100
这题当时没写出来,一直想基于矩阵的下标使用循环完成,因为对于顺时针循环,横纵坐标x, y的变化特点是x, y先分别自增,然后分别自减。当时因为在边界值这块没处理好代码一直没跑起来。后来面试完才想起来切片实现就不用太考虑边界值的问题了。下面分别按照切片的方式和动态调整边界值的方式重新解下这道题。
import pandas as pd
import numpy as np
matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
def solution1(matrix):
"""
方法一:通过切片方式实现
"""
row, col = len(matrix), len(matrix[0])
matrix = np.asarray(matrix).reshape(row, col)
output = []
while len(matrix) > 0: # 无论横着切还是竖着切,当二维矩阵被切完时会回退成一维空数组
# top 注意切片取值范围[)
for i in range(col):
output.append(matrix[0][i])
matrix = matrix[1:]
# right
if len(matrix) > 0:
for i in range(row - 1):
output.append(matrix[i][-1])
matrix = matrix[:, :-1]
# bottom
if len(matrix) > 0:
for i in reversed(range(col - 1)):
output.append(matrix[-1][i])
matrix = matrix[:-1]
# left
if len(matrix) > 0:
for i in reversed(range(row - 2)):
output.append(matrix[i][0])
matrix = matrix[:, 1:]
if len(matrix) > 0:
row, col = len(matrix), len(matrix[0])
else:
return output
def solution2(matrix):
"""
方法二:通过矩阵的上下左右四个边界值,每遍历完一个边界动态的调整该边界的边界值实现
"""
row, col = len(matrix), len(matrix[0])
matrix = np.asarray(matrix).reshape(row, col)
top, bottom, left, right = 0, row - 1, 0, col - 1
output = []
while left <= right and top <= bottom:
# 刚进入while循环可以不用卡边界,此时边界值还未调整
# 遍历上边界,+1是因为range取值[),后面-1也是同理
for i in range(left, right + 1):
output.append(matrix[top][i])
top += 1
# 上下遍历时需要卡左右边界没有互相越界
# 遍历右边界
if left <= right:
for i in range(top, bottom + 1):
output.append(matrix[i][right])
right -= 1
# 左右遍历卡上下边界未越界
# 遍历下边界
if top <= bottom:
for i in range(right, left - 1, -1):
output.append(matrix[bottom][i])
bottom -= 1
# 遍历左边界
if left <= right:
for i in range(bottom, top - 1, -1):
output.append(matrix[i][left])
left += 1
return output
print(f"方法1:{solution1(matrix)}")
print(f"方法2:{solution2(matrix)}")
2.两表取差集
The difference(Relative Complement) between two sets A and B is defined as A - B := {x|x ∈ A ∧ x ∉ B}. Assume that the set allows duplicate elements. For example, the difference between A = (5, 6, 6, 7) and B = (6, 7, 8) is A - B = (5, 6).
Consider each row in a Table as element of the set. Given two Tables t1 and t2, return the difference t1 - t2.
Note that column names in two Tables are identical.
Example 1:
Input:
+-------------+
| t1 |
+------+------+
| col1 | col2 |
+------+------+
| 1 | 3 |
| 1 | 4 |
| 1 | 4 |
| 2 | 5 |
| 4 | 5 |
+------+------+
+-------------+
| t2 |
+------+------+
| col1 | col2 |
+------+------+
| 1 | 3 |
| 1 | 4 |
| 1 | 6 |
| 2 | 5 |
| 3 | 5 |
+------+------+
Output:
+-------------+
| output |
+------+------+
| col1 | col2 |
+------+------+
| 1 | 4 |
| 4 | 5 |
+------+------+
Example 2:
Input:
+-------------+
| t1 |
+------+------+
| col1 | col2 |
+------+------+
| 1 | 3 |
| 1 | 4 |
| 1 | 4 |
+------+------+
+-------------+
| t2 |
+------+------+
| col1 | col2 |
+------+------+
| 1 | 3 |
| 1 | 4 |
| 1 | 4 |
| 1 | 4 |
| 3 | 5 |
+------+------+
Output:
+-------------+
| output |
+------+------+
| col1 | col2 |
+------+------+
+------+------+
面试中用的最简单直接的方式解决,依次判断t1中的元素是否在t2中,在的话都移出去:
import pandas as pd
# Example 1:
t1 = pd.DataFrame(data=[[1, 3], [1, 4], [1, 4], [2, 5], [4, 5]], columns=['col1', 'col2'])
t2 = pd.DataFrame(data=[[1, 3], [1, 4], [1, 6], [2, 5], [3, 5]], columns=['col1', 'col2'])
def solution(t1, t2):
list1 = t1.values.tolist()
list2 = t2.values.tolist()
res = []
for value in list1:
if value in list2:
list2.remove(value) # remove方法会删掉第一次出现的指定值value
else:
res.append(value)
return pd.DataFrame(data=res, columns=['col1', 'col2'])
print(solution(t1, t2))
这题一开始我想用SQL实现,但是因为两个表里都可以有重复数据,比如说对于数据A,t1表有两个A,t2表有一个A,那么关联的时候t1的两个A都能和t2的一个A关联上,而根据题意,t1两个A减去t2一个A,还应剩下一个A,当时的卡点在这。导致SQL的实现没有完成,后被提示开窗函数,才想起来可以通过row_number为相同的A打上序号标签,关联的时候加上序号限制就可以了。
下面是具体代码实现:
import pandas as pd
from pandasql import sqldf
"""
因为涉及到开窗函数,所以不能仅通过pandas中的join完成需求。查看pandas的api发现并不能直接基于df写sql
查看资料后发现可以通过引入第三方库pandasql实现。pandasql文档:https://pypi.org/project/pandasql/0.7.3/
"""
# Example 1:
t1 = pd.DataFrame(data=[[1, 3], [1, 4], [1, 4], [2, 5], [4, 5]], columns=['col1', 'col2'])
t2 = pd.DataFrame(data=[[1, 3], [1, 4], [1, 6], [2, 5], [3, 5]], columns=['col1', 'col2'])
def solution(t1, t2):
pysqldf = lambda q: sqldf(q, globals())
return pysqldf("""
select
distinct t1.col1, t1.col2
from (
select
*,
row_number() over(partition by col1,col2) as rn
from t1
) t1 left join (
select
*,
row_number() over(partition by col1,col2) as rn
from t2
) t2 on t1.col1=t2.col1 and t1.col2=t2.col2 and t1.rn=t2.rn
where t2.col1 is null;
""")
print(solution(t1, t2))
但是上面的代码总是报SQL语法错误,查资料后说的是sqlite3的版本低了,不支持开窗函数,从3.25.0开始支持,我的是3.21.0,升级标准库还需要升级解释器,为了方便直接通过下面代码把数据同步到mysql中实现:
import pandas as pd
from sqlalchemy import create_engine
# Example 1:
t1 = pd.DataFrame(data=[[1, 3], [1, 4], [1, 4], [2, 5], [4, 5]], columns=['col1', 'col2'])
t2 = pd.DataFrame(data=[[1, 3], [1, 4], [1, 6], [2, 5], [3, 5]], columns=['col1', 'col2'])
engine = create_engine('mysql+pymysql://root:123456@localhost/demo')
t1.to_sql('t1', engine, index=False)
t2.to_sql('t2', engine, index=False)
select
distinct t1.col1, t1.col2
from (
select
*,
row_number() over(partition by col1,col2) as rn
from t1
) t1 left join (
select
*,
row_number() over(partition by col1,col2) as rn
from t2
) t2 on t1.col1=t2.col1 and t1.col2=t2.col2 and t1.rn=t2.rn
where t2.col1 is null;
二、百度外包(一面)
做地图数据处理的。
1.介绍一个SQL处理过的复杂场景问题
当时介绍的是基于病人初次确诊的时间线,取每个病人离此时间线最近的一次就诊记录。优先取该时间线之前的记录。解法可以参考https://leetcode.cn/problems/product-price-at-a-given-date/description/
后来想想,还有个更复杂更适合描述的场景。医嘱表(大约20+亿条数据)中每个病人会存在很多条医嘱记录,每条医嘱里可能涉及到一种药品名,每种药品名可能有很多种写法或者别称,由医学提供药名的标准名称和所有别名之间的映射关系。下面的两个场景都是限定使用且仅使用了给定的药物组合,缺了某种药物不行,多使用了额外的某种药物也不行。
一:找出仅使用了某种药物标准名组合的患者。
思路是先基于case when+正则,新增一列给每条医嘱打上一个药物标准名名称,然后根据患者ID group分组,把所有的标准名group_concat拼接在一起。最后通过多个if语句结合正则新增多列,每一列为1时表示使用了这列代表的药物,为0时代表没有使用,最后在筛选时直接在列之间相加求和进行限定就行了
二:找出一共有哪些种药物组合,每种药物组合对应的患者人数是多少?
同样给每条记录打上药物标准名,然后根据患者ID group分组把这些标准名拼接在一起。但是后续就不适合新增列代表是否使用某种药物了,因为药物组合实在太多。这个时候可以对这个拼接的结果再次group分组,但是这里有一个问题就是在上一步的group中,如果两个患者全过程使用的药物一样,但是使用药物的先后顺序不一样,就会导致最终拼接在一起的结果不一样,所以在第二次分组前需要指定一种药物排列顺序对上一步的拼接结果重新排序拼接。当时的实现方式是先使用hive里的split函数将拼接结果拆分为数组,再用sort_array函数对数组中的药品名按照字典顺序排序,最后再concat_ws重新拼接。基于这个排序后的结果分组统计就不会有问题了
2.udf定义和注册的全流程
https://blog.youkuaiyun.com/atwdy/article/details/128319034
3.pandas的dataframe中,统计一个列中不同值的出现次数用哪个方法
1)df['col'].value_counts()
2)df.groupby('col').count()
4.pyspark实现WordCount
大致给的思路是:map–>flatMap–>reduceByKey–>collect。正确思路:①flatMap(map+平铺,map将每行数据按照空格切分返回3个列表,flat平铺将3个列表合并为一个列表)–>②map(列表中的每个元素变为(key, 1)
这样的元组方便计数)–>③reduceByKey(对相同key的记录先后进行本地分区和跨分区聚合)–>collect(收集rdd结果,触发算子执行)
卡点:
flatMap和map没有深刻理解区分,深层是对文件读取过程中的细节模糊,文件可以看成是行的集合,读取过程中如果不是指定字节数读取的那种方式,而是类似于for line in file
的这种方式读取,每次读取的都是一行。
下面重新实现下,本地创建测试数据:
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.master("local[*]") \
.appName("WordCount") \
.getOrCreate()
sc = spark.sparkContext
# 读HDFS路径格式:'hdfs:///namenode-host:port/path...'
rdd_src = sc.textFile(r'file:///D:\DevSoftware\Pycharm\workspace\SparkDemo\base\data.txt')
res = rdd_src.flatMap(lambda line: line.split(' ')) \
.map(lambda x: (x, 1)) \
.reduceByKey(lambda total, x: total + x) \
.collect()
print(res)
sc.stop()
spark.stop()
output:
后面又问了下reduceByKey的功能,和groupBykey的区别?
https://blog.youkuaiyun.com/atwdy/article/details/133155108,可以简单概括为:reduceBykey = groupByKey + map和reduce两端的聚合。
也可以使用groupByKey+mapValues实现单词计数:
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.master("local[*]") \
.appName("WordCount") \
.getOrCreate()
sc = spark.sparkContext
rdd_src = sc.textFile(r'file:///D:\DevSoftware\Pycharm\workspace\SparkDemo\base\data.txt')
res = rdd_src.flatMap(lambda line: line.split(' ')) \
.map(lambda x: (x, 1)) \
.groupByKey() \
.mapValues(sum) \
.collect()
print(res)
sc.stop()
spark.stop()
5.合并两个有序列表
面试中给的解法:
lst1 = [1, 3, 6, 9]
lst2 = [2, 8, 11, 15]
def solution(lst1, lst2):
len1, len2 = len(lst1), len(lst2)
i = j = 0
res = []
# 双指针 逐位比较 某个列表遍历完时退出循环
while i < len1 and j < len2:
if lst1[i] < lst2[j]:
res.append(lst1[i])
i += 1
else:
res.append(lst2[j])
j += 1
# 将未遍历完的列表剩下的元素直接全部移到新结果之后
if i < len1:
res.extend(lst1[i:])
else:
res.extend(lst2[j:])
return res
print(solution(lst1, lst2))
output:
6.SQL找出薪水比所属经理薪水高的员工
很简单,不说了
7.java编写MR的流程
三、百度外包(二面)
1.介绍过往的项目
主要说下数据的上游和下游,日常工作负责哪些内容,用的什么技术,数据交付时有没有遇到数据质量问题,怎么解决的。
前面就省略,对于数据质量问题,
2.说下对进程和线程的理解,以及CPU核和它们之间的关系
1)进程是资源分配的基本单位,线程是任务调度的基本单位。CPU一个物理核同一时刻只能执行一个线程,注意是线程而非进程。我们常说的并发,其实是线程之间通过快速时间片轮转的方式轮流切换执行,让我们感觉到像是在同时执行。
2)线程依附于进程提供的资源执行,不能脱离进程单独存在,一个线程中至少包含一个主线程,同时也可以包含多个子线程。对于主线程的理解:主线程随进程的创建而创建,是进程用来执行任务的入口,生命周期一般和进程相同,除非当主线程结束后还存在其他非守护线程没结束。
3)对于同属于一个进程的所有线程,每个线程都有自己独立的寄存器,程序计数器,私有堆栈。形象的说,进程像是一间房子,线程是房子里的工人,工人共用房间里的工具(资源)来干活,但是每个工人也得有自己的一个小本本,记录下刚才干了什么,干到哪儿了,接下来该从哪儿开始干。
3.机器学习中,分类算法有哪些?根据什么评估分类效果
逻辑回归、支持向量机、决策树、随机森林、K近邻…,可以通过混淆矩阵或ROC曲线对算法的分类效果进行评估。
混淆矩阵里面的一些指标当时忘了具体怎么计算了,只大致描述,下面重新总结了下:
https://blog.youkuaiyun.com/atwdy/article/details/136976504
4.聚类算法有哪些?以及KMeans算法的实现流程?
聚类算法属于无监督学习算法,只说了下KMeans,还有层次聚类和密度聚类因为不太熟悉就没有多说。
KMeans的实现流程:预先选择K个中心点(肘部法),计算每个点到这些中心点的距离,离谁近就归类到哪一类,在一轮分类完成后,重新计算每个类别的中心点,然后再次计算所有点到这些中心点的距离并归类,重复迭代,直到中心点的位置变化满足某个阈值或者达到指定的迭代次数。
5.知道哪些排序算法?一个包含10亿个值的数组,找出top10的元素,选择哪种排序算法,为什么?以及对应的时间复杂度
关于排序算法:
插入排序:直接插入、二分插入、希尔排序
交换排序:冒泡、快排
选择排序:简单选择、堆排
桶排序:计数排序、基数排序
当时给的答案是堆排序,因为考虑只取top10,不必对整个序列全部排序。堆排序建初堆后,每次取出堆顶元素,然后重新调整下堆,继续取堆顶。后面查资料后选堆排没问题,但是时间复杂度这块当时没说出来,堆排的时间复杂度主要分为建初堆和堆调整这两部分,
Q:为什么不用快排?
快排每次只是给选定的基准元素找到正确的位置,但是并不能保证选择的这个元素就是topN中的元素,因此只能将序列完全排序完后才能选出topN,对于从10亿个值的序列中选topN,没必要将所有元素排序。且快排的最坏时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),此时给定的序列已经有序,
6.代码实现二分查找
面试中用的是递归实现:
lst = [1, 3, 5, 8, 9]
n = 8
# 第一次提交的代码如下,跑出来的结果错误,debug后发现问题:
# 1)没有递归出口
# 2)切片的方式改变了查找的值在原数组中的真实下标位置
# def solution(lst, n):
# length, mid = len(lst), len(lst) // 2
# if lst[mid] == n:
# return mid
# elif lst[mid] < n:
# return solution(lst[mid + 1:], n)
# else:
# return solution(lst[:mid], n)
# 正确答案
def solution(lst, left, right, n):
if right < left:
return
mid = (left + right) // 2
if lst[mid] == n:
return mid
elif lst[mid] < n:
return solution(lst, mid + 1, right, n)
else:
return solution(lst, left, mid - 1, n)
print(solution(lst, 0, len(lst), 8)) # output: 3
也可以用迭代的方式实现:
lst = [1, 3, 5, 8, 9]
n = 8
# 迭代实现
def solution(lst, n):
left, right = 0, len(lst) - 1
while left <= right:
mid = (left + right) // 2
if lst[mid] == n:
return left
elif lst[mid] < n:
left = mid + 1
else:
right = mid - 1
return
print(solution(lst, 3)) # output: 1