导读
刷惯了LeetCode,近日体验了一下牛客网的在线编程系统,这里记录一次某大厂的3道机试题实录,最后居然是满分通过。题目不难,但有一定借鉴意义!
一共3道题,其中前两题难度1星,第三题难度2星,题目难度不大,但都比较经典。话不多说,直接看题!
01 平衡二叉树+后序遍历
这是一道考察二叉树相关操作的题目,主要考点为平衡二叉树的性质和二叉树的前、中、后序遍历。另外,受牛客网编程OJ系统的限制,给定题目的输入并非是一颗构建好的树节点,而只提供了一串序列数值。要求以后序遍历的顺序输出二叉树中的非叶子节点。
例如,在上图中,输入为1-2-3-4-5-6-7-8-9-10-11-12,标准输出应为4-5-2-6-3-1。
解决这一问题,实际上可拆解为以下3个子问题:
根据给定输入数值序列构建一颗平衡二叉树;
获取平衡二叉树中的非叶子节点部分;
后序遍历完成这部分节点的数值输出
对于第一个小问题,实际相当于完成二叉树层序遍历的过程:第一层构建1个节点,第二层构建2个节点,第三层4个节点……,直至用完所有的输入数值。这其实也暗含了平衡二叉树的一个性质:在平衡二叉树中,对于编号为i(i从0开始)的父节点,其左右子节点的编号分别为2i+1和2i+2。想到这里,那么在确定了平衡二叉树中节点个数的情况下,很显然的就知道了有子节点的个数,即:
平衡二叉树中非叶子节点有N//2个,其中N为总节点个数,//为整除
进而,第二个小问题实际上可轻松实现,即首先将输入序列中的数值列表砍半,而后根据层序遍历构建一颗完整的平衡二叉树,得到就是原题中要求的非叶子节点部分。
最后,在得到非叶子节点构成的二叉树的基础上,简单通过递归完成后序遍历输出即可。
给出示例代码如下:
class TreeNode:
# 定义树节点
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def fun(nums):
def buildAVLTree(nums): # 根据输入序列构建平衡二叉树
head = TreeNode(nums[0])
nodes = [head]
i, n = 1, len(nums)
while i < n: # 层序遍历
for node in nodes:
if i < n:
node.left = TreeNode(nums[i])
if i+1 < n:
node.right = TreeNode(nums[i+1])
i += 2
nodes = [n for node in nodes for n in (node.left, node.right) if n]
return head
def postOrder(head): # 以递归形式完成后序遍历
return postOrder(head.left) + postOrder(head.right) + [head.val] if head else []
if len(nums) < 2:
return nums
head = buildAVLTree(nums[:len(nums)//2]) # 提取一半,作为非叶子节点构建平衡二叉树
return postOrder(head)
# 测试样例
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
print(fun(nums))
# [4, 5, 2, 6, 3, 1]
02 双指针+滑动窗口
第二道题目大意是:给定正整数序列和一个目标数值,求正整数序列中加和等于目标数值的最长连续子序列。例如给定正整数序列[1, 2, 3, 4, 2]和目标数值6,由于仅有1+2+3=6和4+2=6两个子序列满足条件,所以最长子序列的长度为3。当不存在目标结果时,返回-1。
值得指出,这里需要注意题目中限定了输入序列是正整数序列,后续有重要应用!
在刚拿到题目看到最长子序列时,由于平日里做惯了最长公共子序列、最长上升子序列等经典动态规划题目,所以直觉想到的也是用动态规划(动态规划仍然是内心深处的软肋,总会心有余悸)。按照动态规划的套路,结合这道题的要求,首先初始化一个N×N大小的结果矩阵,其中第i行第j列的取值代表原输入序列中从i到j的子序列的加和。按照这一定义的数据结构,动态规划转移方程其实就很显然的为:
# 记输入序列为nums[N],动态规划结果矩阵为dp[N][N],则状态转移方程为:
dp[i][j+1] = dp[i][j] + nums[j+1]
显然,这个状态转移方程真的是太简单了,简单到毫无动态规划的味道!自然,按照动态规划中常用的空间优化策略,这个N×N的矩阵可以精简成1×N的单行列表,即仅需记录当前行的实时累加结果即可。再进一步地,发现这个结果仅在相邻两个元素之间产生依赖和传递,进而1×N的结果矩阵可进一步精简为一个标量记录当前结果即可。空间优化完毕。时间效率方面,这里需要两层循环,其中外层i:0->N-1,内层j:i->N-1,是一个O(n2)的时间复杂度。未经深入思考,在提交代码之后居然通过了所有案例!代码如下:
def fun(nums, target):
max_len = -1
for i in range(len(nums)):
sum_ = 0
for j in range(i, len(nums)):
sum_ += nums[j]
if sum_ == target:
max_len = max(max_len, j - i + 1)
return max_len
# 测试样例
nums = [1, 2, 3, 4, 2]
target = 6
print(fun(nums, target))
# 3
当然,这是一个奏效的解决方案,空间复杂度上也是一个最优的版本。然而,时间复杂度上其实可以进一步优化,个中玄机就在于题目中明确指出输入序列是一个正整数。既然是正整数,那么就意味着固定子序列起点i,在不断移动右端点的过程中子序列的加和是严格递增的过程。换言之,对于当前一个固定长度的子序列,若求和小于目标值,则需将右端点j向右移动;若求和大于目标值,此时继续移动右端点是不可行的,而需移动左端点i->i+1,进而实现一个弹性移动的滑动窗口,直至当前求和等于目标结果。每个数值最多遍历2次,进而实现了时间复杂度的降维,即O(n)。给出示例代码如下:
def fun(nums, target):
max_len = -1
l = r = 0
sum_ = nums[0]
while r < len(nums):
if sum_ < target: # 当前累和小于目标值,移动右端点,并增加新右端点数值
r += 1
if r >= len(nums):
break
sum_ += nums[r]
elif sum_ > target: # 当前累和大于目标值,移动左端点,并减去原左端点数值
sum_ -= nums[l]
l += 1
else: # 找到一组结果,更新最大长度,同时移动左右端点,并减去原左端点数值,增加新右端点数值
max_len = max(max_len, r - l + 1)
sum_ -= nums[l]
l += 1
r += 1
if r >= len(nums):
break
sum_ += nums[r]
return max_len
# 测试样例
nums = [1, 2, 3, 4, 2]
target = 6
print(fun(nums, target))
# 3
03 广度优先遍历
第三道题目是一个标准的广度优先遍历,虽然标记2星,但实际上难度与前两题大致相当,个人在实际作答中也是一遍出炉未进行任何深入思考和调试。
题目大意描述如下:给定仅包含0或1两类数值的N×N矩阵,其中0代表健康细胞,1代表病毒细胞,病毒细胞每分钟向周边相邻细胞进行扩散(不含对角线相邻),求解给定数值下需经过多长时间扩散到所有细胞。若原始均为健康细胞或均为病毒细胞,则返回-1。例如在下图中,需要2分钟即可完成四周向中心点的扩散传播,即返回结果为2。
实际上,这种题目在LeetCode中有很多相似题目,基本都是一个套路:广度优先。同时,由于涉及到矩阵坐标的四周传播,所以仅需一个迭代坐标列表即可。
由于本题过于经典和常规,下面直接给出示例代码:
def fun(nums):
def isValid(x, y): # 判断给定坐标是否在合法范围
return 0 <= x < len(nums) and 0 <= y < len(nums)
locs = [(i, j) for i in range(len(nums)) for j in range(len(nums)) if nums[i][j] == 1] # 记录初始状态下1的坐标列表
if len(locs) == 0 or len(locs) == len(nums) ** 2: # 初始即为全0或全1
return -1
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] # 模拟4个方向传播
res = 0
while locs:
temp = []
for i, j in locs:
for dx, dy in directions: # 对当前最新发现的病毒细胞进行遍历4个方向
x = i + dx
y = j + dy
if isValid(x, y) and nums[x][y] == 0: # 发现周边的健康细胞,记录坐标到新列表中并将其置为1
nums[x][y] = 1
temp.append((x, y))
if temp: # 若实际发生传播,结果+1
res += 1
locs = temp
return res
# 测试样例
nums = [[1, 0, 1], [0, 0, 0], [1, 0, 1]]
print(fun(nums))
# 2
通过这3道经典题型,你是否有所收获呢?
相关阅读: