某大厂机试,面试手撕

---------------------------------------------------------------------------------------------------------------------------------

周五晚上玩了会游戏,没什么感觉,看了一个小时的《海子诗集》,但是好像没看懂。听了很多歌,里面有句歌词“从来没爱过,所以爱错,我从哪里起飞,从哪里降落,多少不能原谅的错,却不能重来过”,更多是这句“远离家乡,不胜唏嘘,幻化成秋夜,而我却像落叶归根,坠在你心间”,让我触动。

---------------------------------------------------------------------------------------------------------------------------------

笔试的第一个题目(回忆):

题目描述:

        给一个二叉树,使用数组存放,比如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

---------------------------------------------------------------------------------------------------------------------------------

手撕第二题(二面):

题目描述:22. 括号生成 - 力扣(LeetCode)

数字 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

时间复杂度基本是最好的了。

两个手撕题目都比较简单,但是八股文挺难的,准备的小伙伴刷题的同时,记得备下八股文,同时还需要能够能用。

最后,希望大家机试,面试都顺利通过,如果文章有问题,欢迎联系我修正。

——————————————————————————————————————————

        海子诗集读了大半了,我总感觉海子不应该是哪个时代的人,海子很喜欢太阳,是燎原的烈火,是希望的天堂,但是唯独没能将他救赎。很喜欢那一句“今夜我不关心人类,只想你”,还有那一句“我有一所房子,面朝大海,春暖花开”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值