青训营X豆包MarsCode 学习笔记:验证火车驶入和驶出顺序 | 豆包MarsCode AI刷题

81 阅读6分钟

一、题目背景与分析

题目描述: 小F在观察火车驶入和驶出休息区的顺序时,注意到休息区的结构类似于栈,即遵循先进后出的规则。她记录了火车驶入和驶出的顺序,并希望验证这些顺序是否可能实际发生。火车在进入休息区后可以按顺序驶入或停留,然后根据休息区的规则依次驶出。你的任务是帮助小F验证所记录的火车驶入和驶出顺序是否能够被满足。

示例

  • 如果火车的驶入顺序是 1 2 3,驶出顺序是 3 2 1,这是可能的。
  • 如果驶出顺序是 3 1 2,则是不可能的。

二、思路分析

  1. 栈的基本特性

    • 栈是一种只能在一端进行插入或删除的线性表,其特点是后进先出(LIFO)。
    • 在这个问题中,火车进入休息区可以看作是入栈操作,离开休息区可以看作是出栈操作。
  2. 模拟过程

    • 我们需要一个栈来模拟火车进入和离开休息区的过程。
    • 遍历驶出顺序,检查当前火车是否可以按照栈的规则离开休息区。
    • 如果栈顶的火车编号与当前驶出的火车编号相同,则弹出栈顶元素。
    • 否则,继续将驶入顺序中的火车入栈,直到找到匹配的火车或驶入顺序结束。
    • 如果在遍历完驶出顺序后,所有火车都能按顺序离开休息区,则返回 True,否则返回 False

三、代码实现

def solution(n: int, a: list, b: list) -> bool:
    # 初始化一个栈
    stack = []
    # 初始化驶入顺序的索引
    i = 0
    
    # 遍历驶出顺序
    for out in b:
        # 如果栈顶元素与当前驶出顺序的火车匹配
        if stack and stack[-1] == out:
            # 弹出栈顶元素
            stack.pop()
        else:
            # 否则,继续驶入火车直到匹配或驶入顺序结束
            while i < n and a[i] != out:
                # 将当前火车驶入休息区
                stack.append(a[i])
                i += 1
            
            # 如果驶入顺序结束且未找到匹配的火车
            if i == n:
                return False
            
            # 找到匹配的火车,驶入并驶出
            i += 1
    
    # 如果所有火车都能按驶出顺序驶出,返回 True
    return True

if __name__ == '__main__':
    print(solution(3, [1, 2, 3], [1, 2, 3]) == True)
    print(solution(3, [1, 2, 3], [3, 2, 1]) == True)
    print(solution(3, [1, 2, 3], [3, 1, 2]) == False)

四、知识点总结

1. 定义

栈是一种只能在一端进行插入或删除的线性表。这一端称为栈顶(Top),另一端称为栈底(Bottom)。栈的基本操作包括:

  • 入栈(Push):在栈顶添加一个元素。
  • 出栈(Pop):移除栈顶的元素。
  • 获取栈顶元素(Top):返回栈顶的元素,但不移除它。
  • 判断栈是否为空(Empty):检查栈中是否有元素。

2. 特点

  • 后进先出(LIFO):最后入栈的元素最先出栈。
  • 操作集中:所有的插入和删除操作都在栈顶进行,这使得栈的操作非常高效。

栈的应用场景

1. 表达式求值

在计算表达式的值时,栈可以用来处理运算符的优先级和括号。例如,中缀表达式转换为后缀表达式(逆波兰表示法)的过程中,栈可以用来存储操作数和操作符。

示例: 将中缀表达式 3 + 4 * 2 / (1 - 5) 转换为后缀表达式:

  • 使用两个栈:一个用于操作数,一个用于操作符。
  • 遍历表达式,遇到数字时将其压入操作数栈,遇到操作符时根据优先级决定是否将栈顶操作符弹出并执行。

2. 函数调用和递归

在程序执行过程中,函数调用和递归调用都会使用栈来保存函数的局部变量和返回地址。每次调用函数时,当前的局部变量和返回地址会被压入栈中,函数返回时再从栈中弹出这些信息。

示例: 递归计算阶乘 n!

  • 每次递归调用时,当前的 n 和返回地址会被压入栈中。
  • 当递归返回时,从栈中弹出这些信息,恢复调用前的状态。

3. 括号匹配

栈可以用于检查括号是否匹配。遍历字符串时,遇到左括号将其压入栈中,遇到右括号时检查栈顶是否为对应的左括号。如果是,则弹出栈顶元素,否则括号不匹配。

示例: 检查括号序列 [(())] 是否匹配:

  • 遍历字符串,遇到 [( 时将其压入栈中。
  • 遇到 ]) 时检查栈顶是否为对应的左括号,如果是则弹出,否则不匹配。

4. 浏览器的回退按钮

浏览器的回退按钮功能也可以用栈来实现。每次用户访问一个新页面时,将当前页面的URL压入栈中。当用户点击回退按钮时,从栈中弹出当前页面的URL,返回到上一个页面。

示例: 用户访问页面 A -> B -> C:

  • 访问 A 时,栈为 [A]
  • 访问 B 时,栈为 [A, B]
  • 访问 C 时,栈为 [A, B, C]
  • 点击回退按钮,栈变为 [A, B]

5. 深度优先搜索(DFS)

在图的深度优先搜索中,栈可以用来存储待访问的节点。每次从栈中弹出一个节点,访问该节点并将它的邻接节点压入栈中。

示例: 在一个无向图中进行深度优先搜索:

  • 初始化栈,将起始节点压入栈中。
  • 每次从栈中弹出一个节点,访问该节点并将它的未访问邻接节点压入栈中。
  1. 算法思路
    • 使用一个栈来模拟火车进入和离开休息区的过程。
    • 遍历驶出顺序,检查当前火车是否可以按照栈的规则离开休息区。
    • 通过不断调整栈的状态,验证驶入和驶出顺序是否一致。

五、学习心得

  1. 理解问题的本质

    • 本题的关键在于理解栈的特性及其在实际问题中的应用。通过将火车的进入和离开过程抽象成栈的操作,可以简化问题的复杂度,使其更容易解决。
  2. 代码实现的细节

    • 在实现过程中,需要注意边界条件的处理,如栈为空时的处理、驶入顺序结束时的处理等。
    • 通过逐步调试和测试,可以确保代码的正确性和鲁棒性。
  3. 算法的优化

    • 虽然本题的解法已经较为高效,但在处理大规模数据时,仍需考虑算法的时间复杂度和空间复杂度。
    • 通过优化栈的操作和减少不必要的比较,可以进一步提高算法的性能。
  4. 实际应用的拓展

    • 本题的解法不仅适用于火车的进出问题,还可以应用于其他需要逆序处理的场景,如括号匹配、表达式求值等。
    • 掌握栈的基本操作和应用,对于解决类似问题具有重要的意义。

通过这次学习,我不仅加深了对栈这一数据结构的理解,还掌握了如何将抽象的数据结构应用于实际问题中。这种从理论到实践的学习过程,使我更加自信地面对各种编程挑战。在未来的学习和工作中,我将继续探索更多有趣的问题,不断提升自己的编程能力和解决问题的能力。