---------------------------------------------------------------------------------------------------------------------------------
周五晚上玩了会游戏,没什么感觉,看了一个小时的《海子诗集》,但是好像没看懂。听了很多歌,里面有句歌词“从来没爱过,所以爱错,我从哪里起飞,从哪里降落,多少不能原谅的错,却不能重来过”,更多是这句“远离家乡,不胜唏嘘,幻化成秋夜,而我却像落叶归根,坠在你心间”,让我触动。
---------------------------------------------------------------------------------------------------------------------------------
笔试的第一个题目(回忆):
题目描述:
给一个二叉树,使用数组存放,比如tree_1 = [2,,4,,,3,1],其中下标从1开始,则下标j的节点的叶子节点的下标位,左节点2*j,做节点2*j+1,如果某个位置为空,则代表无此节点。输出根节点到最小叶子节点的路径。
样例:
输入 :[2,,4,,,3,1]
输出:[2,4,1]
注:树的层数不超过七层
时间限制:Python 1s
空间限制:256 MB
题目分析:
这个题目本身比较简单,就是一个遍历的问题,这里可以选择前序,中序,后序,层序遍历都可以。但是,会不会操作时间限制。
解题思路:
用一个栈维护遍历路径,一个min_ 维护最小叶子节点,path保存当前最小叶子节点路径,每次遇到叶子节点,和当前最小叶子节点比较,如果小于则更新叶子节点,和路径。
# 需要自己处理输入输出
# 维护一个全局变量,用来判断当前叶子节点和记录的最小叶子节点的大小关系
min_ = [float('inf')]
cache = []
def preorder(path, root, nums, n): # root 为根节点的下标(从 1 开始),n 为数组长度
# 到达“空节点”或越界:此时上一层 path[-1] 就是叶子
if root > n or not nums[root - 1]: # <-- 统一用 root-1 取值
if path and path[-1] < min_[0]: # <-- 先判断 path 非空,再比较
global cache
min_[0] = path[-1] # <-- 覆盖而不是 append
cache = path[:] # 记录从根到该叶子的路径(或仅保留叶子也可)
return
# 当前节点存在,压栈并下探左右子树
val = int(nums[root - 1]) # 若是数字,用 int;若是字母可去掉 int()
path.append(val)
preorder(path, root * 2, nums, n)
preorder(path, root * 2 + 1, nums, n)
path.pop()
if __name__ == "__main__":
tree = list(input().split(","))
# print(f'tree is {tree}')
preorder([], 1, tree, len(tree))
print(cache) # 若只想输出最小叶子:print(min_[0] if min_[0] != float('inf') else None)
---------------------------------------------------------------------------------------------------------------------------------
笔试第二题:
双端队列头部顺序移除数据的排序次数
题目描述:
给一个数字n,构造一个双端队列,可以从头部和尾部插入数据1-n,但是只能从头部移除,要求移除顺序为1-n。因为这个输入比较复杂,没找到原题,这里就不展现了,博主也忘记了。
---------------------------------------------------------------------------------------------------------------------------------
笔试第三题:
题目描述:
以字符串b{a,a{c,a}}这种形式给出二叉树,其中b{,} 逗号前为左子树,后面为右子树,给出的二叉树中序遍历字符串形式输出。
样例:
输入:a{b{d{h{p,q},i{r,s}},e{k{t,u},l{v,w}}},c{f{m{x,y},n{z,A}},g{o{B,C},p{D,E}}}}
输出:phqdrisbtkuevlwaxmyfznAcBoCgDpE
题目分析:
如果是标准的二叉树的结构,前序,中序,后序遍历都会比较简单,也是必会的:
节点内容:
"""
class Node:
def __init__(self,data):
self.val = data
self.left = None
self.right = None
"""
前序遍历
# 前序遍历:result 是一个可变类型的列表
def preorder(root,result: List[int]): # 这里的递归的根节点都是上面注释部分的这种结构,就是标准的节点
if root: #如果节点存在,直接访问
result.append(root.val)
# 访问左节点
if root.left:
preorder(root.left,result)
# 访问右节点
if root.right:
preorder(root.right,result)
中序遍历:
# 中序遍历
def inorder(root,result: List[int]):
if root:
# 访问左节点
if root.left:
preorder(root.left,result)
# 中间访问节点内容
result.append(root.val)
# 访问右节点
if root.right:
preorder(root.right,result)
后序遍历:
# 后序遍历
def preorder(root,result: List[int]):
if root:
# 访问左节点
if root.left:
preorder(root.left,result)
# 访问右节点
if root.right:
preorder(root.right,result)
# 最后取值
result.append(root.val)
三个代码快都是标准的前中后序遍历,但是本题不能直接这样使用,因为给的并不是树,而是字符串,这里如果自己建树,会比较复杂,而且,建树的过程就是遍历的过程,所以没有必要。
观察这个格式:b{a,a{c,a}}
节点b的下一层是 a,a{c,a}
此时,左子树是a,右子树是a{c,a} 是不是按照“,”分割字符串就可以得到左右两个子树了呢,是的,但也不是,如果直接分割,结果是[ "a", "a{c", "a"],因为直接分割得到结果,并不如所愿。
那这里怎样分割呢,就想分割出来的就是左右子树,这样就可以访问节点了。
如果观察,会发现,如果每次我们找到下一层的时候,都剥离上一层的,得到a,a{c,a}这样的,那么,这一层中的有且只有一个“,”出现的时候,其前缀没有未匹配的括号。
举几个例子:
a{b{c,d},e{f,g{h,i}}}
到第二层:
b{c,d},e{f,g{h,i}}
这个时候分割左右子树: 对于c和d中间的逗号“,”前面有一个尚未匹配的左括号(b和c之间),第二个逗号("d}"和 e之间)出现的时候,前面括号全部匹配完成,这个就是左右节点的分割处
到第三层:
c,d 和f,g{h,i}
也是同理,而且,一旦第一个合法括号出现了,就分割,不再寻找下一个,然后到下一层再次分割。
这里怎么确定有没有尚未匹配的括号呢,或者怎么确定当前逗号就是合法的呢,这里使用栈来模拟括号匹配,如果当前逗号时,栈为空,就可以认定当前逗号合法分割左右子树。如果没有在一分钟内想到,就是栈的时候 不够熟,可以加强一下,做一做括号匹配,计算器等题目。
def get_left_right_tree(s: str):
ca = [] # 栈
for i, j in enumerate(s):
if j == "{": # 入栈
ca.append(j)
elif j == "}": # 弹出栈顶,匹配
ca.pop()
if j == "," and not ca: # 栈为空,合法
return s[:i], s[i + 1:]
def inorder(tree: str, result):
# tree是一个字符串,result是一个列表
if not tree: # 为空直接返回
return
# 获取当前节点值,比如 a{b{c,d},e{f,g{h,i}}}中的a,同时去除第一个和最后一个括号
node = tree[0]
if len(tree) > 2: # 还有子树的情况下得到下一层,如果只是一个c这样的,无须到下一层
cache = tree[2:-1] # 如果是第二层的话,就是b{c,d},e{f,g{h,i}}
# 分割左右子树
left, right = get_left_right_tree(cache)
# 这里添加供查看的,调试的。
print(f'left is {left}')
print(f'right is {right}')
# 中序遍历左子树
inorder(left, result)
# 当前节点中序添加
result.append(node)
# 中序遍历右子树
inorder(right, result)
else: # 如果是叶子节点,直接加入
result.append(node)
if __name__ == "__main__":
# 输入输出
trees = input()
result_ = []
inorder(trees, result_)
print("".join(result_))
"""
a{b{d{h{p,q},i{r,s}},e{k{t,u},l{v,w}}},c{f{m{x,y},n{z,A}},g{o{B,C},p{D,E}}}}
left is b{d{h{p,q},i{r,s}},e{k{t,u},l{v,w}}}
right is c{f{m{x,y},n{z,A}},g{o{B,C},p{D,E}}}
left is d{h{p,q},i{r,s}}
right is e{k{t,u},l{v,w}}
left is h{p,q}
right is i{r,s}
left is p
right is q
left is r
right is s
left is k{t,u}
right is l{v,w}
left is t
right is u
left is v
right is w
left is f{m{x,y},n{z,A}}
right is g{o{B,C},p{D,E}}
left is m{x,y}
right is n{z,A}
left is x
right is y
left is z
right is A
left is o{B,C}
right is p{D,E}
left is B
right is C
left is D
right is E
phqdrisbtkuevlwaxmyfznAcBoCgDpE
"""
在写博客的时候,我用GPT给出一个版本的答案如下(没有验证正确性):
# -*- coding: utf-8 -*-
import sys
from typing import Optional, Tuple
class Node:
def __init__(self, val: str):
self.val = val
self.left: Optional["Node"] = None
self.right: Optional["Node"] = None
def inorder(root: Optional[Node]) -> str:
if not root:
return ""
return inorder(root.left) + root.val + inorder(root.right)
def parse_tree(s: str) -> Node:
"""
解析形如 a{b{c,d},e} 的表达式为二叉树。
语法:
node := VALUE ('{' node ',' node '}')?
VALUE := 由字母/数字/下划线组成的一个或多个字符
"""
i, n = 0, len(s)
def skip_ws():
nonlocal i
while i < n and s[i].isspace():
i += 1
def parse_value() -> str:
"""读取结点值(至少一个字符的 [A-Za-z0-9_] 序列)"""
nonlocal i
skip_ws()
start = i
while i < n and (s[i].isalnum() or s[i] == '_'):
i += 1
if start == i:
raise ValueError(f"期待结点值,但在位置 {i} 读取到: {s[i:i+10]!r}")
return s[start:i]
def parse_node() -> Node:
nonlocal i
val = parse_value()
node = Node(val)
skip_ws()
if i < n and s[i] == '{':
i += 1 # consume '{'
node.left = parse_node()
skip_ws()
if i >= n or s[i] != ',':
raise ValueError(f"缺少逗号 ',' 于位置 {i}")
i += 1 # consume ','
node.right = parse_node()
skip_ws()
if i >= n or s[i] != '}':
raise ValueError(f"缺少右花括号 '}}' 于位置 {i}")
i += 1 # consume '}'
return node
root = parse_node()
skip_ws()
if i != n:
# 输入中如果还有多余字符,给出提示(也可以选择忽略)
raise ValueError(f"解析后仍有未消费字符:{s[i:]!r}")
return root
def solve_line(line: str) -> str:
root = parse_tree(line.strip())
return inorder(root)
if __name__ == "__main__":
# 读取一行输入;也可以把示例字符串直接赋给 line 测试
if sys.stdin.isatty():
# 示例
line = "a{b{d{h{p,q},i{r,s}},e{k{t,u},l{v,w}}},c{f{m{x,y},n{z,A}},g{o{B,C},p{D,E}}}}"
print(solve_line(line)) # 期望:phqdrisbtkuevlwaxmyfznAcBoCgDpE
else:
line = sys.stdin.read().strip()
print(solve_line(line))
---------------------------------------------------------------------------------------------------------------------------------
手撕第一题(一面):
题目描述:209. 长度最小的子数组 - 力扣(LeetCode)
(这是力扣上的209题,虽然是中等难度题目,但是本身难度简单)
给定一个含有n个正整数的数组和一个正整数target,找出该数组中满足其总和大于等于 target的长度最小的子数组[numsl, numsl + 1, ..., numsr - 1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0。
提示:
1 <= target <= 10 ^ 9
1 <= nums.length <= 10 ^ 5
1 <= nums[i] <= 10 ^ 4
题目分析:
找寻一个最短的子数组,其和大于等于target,这是个滑动窗口的标准题目,当然也可以使用单调栈来做。但是不管是滑动窗口还是单调栈,如果直接使用sum()求和,时间都会浪费很多,升值超时。这里使用前缀和来求解,可以在o(n)时间内完成。(优化后可以到o(n+log n))。
def calculate(nums,target): # 力扣刷多了,都习惯使用面向过程的函数来解决
min_ = float("inf") # 定义最小长度,如果最后此长度依旧为无穷,则没有满足的子数组
prefix_sum = 0 # 前缀和,指针每次向前滑动,则加一个元素的值
# 使用双指针来模拟滑动窗口或者单调栈
left = 0
right = 0
n = len(nums)
while right < n: # 在到达最后一个元素前
prefix_sum += nums[right] # 前缀和,每次添加一个元素
while prefix_sum >= target: # 如果满足条件,依次记录长度,然后将left指针向后移动,减少窗口数量,再次判断
min_ = min(min_,right - left + 1) # 1,0
prefix_sum -= nums[left]
left += 1
right += 1
return min_ if min_ != float("inf") else 0
if __name__ == "__main__":
# 手撕的样例是面试官给的,好几个,这里给出最后一个
target = 11
numbers = [1,1,1,1,1,1,1,1]
print(calculate(numbers,target))
力扣可执行代码:
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
min_ = float("inf") # 定义最小长度,如果最后此长度依旧为无穷,则没有满足的子数组
prefix_sum = 0 # 前缀和,指针每次向前滑动,则加一个元素的值
# 使用双指针来模拟滑动窗口或者单调栈
left = 0
right = 0
n = len(nums)
while right < n: # 在到达最后一个元素前
prefix_sum += nums[right] # 前缀和,每次添加一个元素
while prefix_sum >= target: # 如果满足条件,依次记录长度,然后将left指针向后移动,减少窗口数量,再次判断
min_ = min(min_,right - left + 1) # 1,0
prefix_sum -= nums[left]
left += 1
right += 1
return min_ if min_ != float("inf") else 0
---------------------------------------------------------------------------------------------------------------------------------
手撕第二题(二面):
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3 输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1 输出:["()"]
题目分析:
这题目是中等难度的题目,也是必会的递归回溯的经典题目类型。本题的剪枝策略才是这个题目的关键,如果直接递归回溯,去选择“(”和“)”,会导致大量的非法的组合比如[")("]的出现,这个时候如果写一个函数isValid() 来判断,会消耗大量的时间,因为必须使用栈来判断,会很慢。因此这里的有一个剪枝策略,就是所有合法的括号组合,任何一个前缀中,左括号数量大于等于右括号数,同时,只要满足这个条件的成对的(左右口号数量一样)的组合都是合法的。这个剪枝策略在这里的使用非关键。
解题思路:
使用递归回溯,每次添加括号进去,但是添加之前和之后,必须满足剪枝策略的任意前缀中,左括号数大于等于右括号数量。因此第一个添加进来的就不可能是“)”,因为添加之后的前缀“)”不满足合法条件(剪枝策略)。
在满足左括号数量小于n时,每次递归都添加左括号,直到不能满足条件,这时候添加右括号,直到满足回溯条件,这时回撤添加的括号
def calculate(n):
result = []
def backtrack(path,num,left,right):
if num == 2*n: # 当左右括号数量和为2*n时,结束这次递归,并把结果保存到结果里
nonlocal result
result.append("".join(path))
return
# 每次都先添加左括号,保证合法性
if left < n: # 只需要 n 个
path.append("(") # 添加
backtrack(path,num + 1,left+1,right) # 递归添加
path.pop() # 回溯
if right < left: # left 控制right不会超过n
path.append(")")
backtrack(path,num+1,left,right+1)
path.pop()
backtrack([],0,0,0)
return result
if __name__ == "__main__":
n = int(input())
cache = calculate(n)
print(cache)
可用于力扣直接运行的代码:
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
result = []
def backtrack(path,num,left,right):
if num == 2*n: # 当左右括号数量和为2*n时,结束这次递归,并把结果保存到结果里
nonlocal result
result.append("".join(path))
return
# 每次都先添加左括号,保证合法性
if left < n: # 只需要 n 个
path.append("(") # 添加
backtrack(path,num + 1,left+1,right) # 递归添加
path.pop() # 回溯
if right < left: # left 控制right不会超过n
path.append(")")
backtrack(path,num+1,left,right+1)
path.pop()
backtrack([],0,0,0)
return result
时间复杂度基本是最好的了。
两个手撕题目都比较简单,但是八股文挺难的,准备的小伙伴刷题的同时,记得备下八股文,同时还需要能够能用。
最后,希望大家机试,面试都顺利通过,如果文章有问题,欢迎联系我修正。
——————————————————————————————————————————
海子诗集读了大半了,我总感觉海子不应该是哪个时代的人,海子很喜欢太阳,是燎原的烈火,是希望的天堂,但是唯独没能将他救赎。很喜欢那一句“今夜我不关心人类,只想你”,还有那一句“我有一所房子,面朝大海,春暖花开”。