前两天有个朋友拿了一道“老鼠出动顺序”的题来问我,说题目特别短,但他第一反应是去模拟两只老鼠的追尾过程,写着写着就乱了。我拿 Python 配合单调栈手推了几组数据之后,发现这题非常适合用来演示单调栈的一种常见形态:相邻元素互相淘汰,栈内始终维持某种单调性。很多人对单调栈的印象停留在“下一个更大元素”“柱状图最大矩形”,但实际上算法题只要出现“后面的东西会吃掉前面的东西”“先完成的候选会取代之前的候选”这类逻辑,都能往单调栈上靠。
这道题的核心场景可以这样理解:洞口在坐标 0,所有老鼠都在正半轴,存在一条只能前后排列的单向通道。每只老鼠有初始位置和奔跑速度,统一朝洞口方向跑。如果后面的老鼠速度足够快,能在前面那只老鼠到达洞口之前追上它,就会把前面的老鼠“吞并”,然后保持自己的速度继续跑。最终从洞口依次出来的不是所有初始老鼠,而是一串“赢家”,题目要求你就按这种顺序输出它们。适合看这篇内容的人,是想把单调栈真正用起来的算法学习者,或者准备笔试面试、想快速搞懂这类追及问题背后公式推导的开发者。
正文我会先把题意翻译成模型,再推导关键不等式,然后给出可复制的 Python 实现,最后把我在实际调试中踩过的坑、以及这个模型的常见变体一起梳理一遍。
1. 先把这个“追及题”翻译成算法题
1.1 规则没有你想的那么复杂
先把物理场景固定下来。洞口在坐标 0 的位置,老鼠在正半轴不同位置,也就是离洞口有远有近。所有老鼠同时开始朝 0 这个方向跑。注意通道是单行的,后面的老鼠不可能超车到前面去,只能追尾。如果后鼠在“前鼠到达洞口之前”追上了前鼠,就把前鼠吞并掉,自己继续按原来的速度往洞口跑。这里有一个很多人第一反应会搞错的规则:吞并之后速度并不变慢,也不会变成前后排队一起慢慢跑,而是后鼠保持自己的速度继续前行。
为什么题目会这样设定?因为如果合体后取两只老鼠中较慢的速度,那么最靠近洞口的老鼠一定最先到达,后面的老鼠不管多快都会被前面慢的堵住,最终所有老鼠出洞顺序就是它们初始位置的先后顺序,没有任何算法含量。所以这道题能做成单调栈,核心前提就是后面的快鼠吞并前鼠后不减速,通俗讲就是“谁快谁赢,快的还能继续快”。
当题目给你 n 只老鼠的初始坐标 x 和速度 v 时,你要回答的并不是它们实际在哪个点追上,而是最后那串出洞事件。比如:
- 老鼠 A 在坐标
