这四个offer四选一,你们觉得选哪个比较好?
刚刷到这个四选一,给我看精神了。
一个是去车企,月到手四万档,年底还有一堆薪,干自动驾驶;一个是去上海中学当数学老师,月薪低一截,但有安家费,稳定感直接拉满;另外俩更猛,一个大模型推理,一个机器人感知,钱也都在四万多,年包看着很香。
这题表面是选offer,其实是选命。
想冲钱和技术上限,那肯定盯着AI和机器人,节奏快,压力也别装看不见。想要牌子和产业落地,车企也不差,自动驾驶这几年一直卷。可要是家里想稳,自己也不想天天被项目追着跑,上海中学那个反而最耐看。
程序员老鬼看完就一个感觉:年轻敢卷选DeepSeek或者宇树,想活得像个人,老师真不丢人。
出界的路径数,别一上来就 DFS 硬搜
这题最容易写歪的地方,不是不会走四个方向,而是把它当成普通迷宫题。
普通迷宫是问你能不能走到某个格子。 这题问的是:球从某个格子出发,最多走 maxMove 步,有多少种走法会冲出边界。
注意,是“冲出去”的那一脚就算一次路径。
比如球在 (0, 0),往上走,出界,算 1 条;往左走,出界,也算 1 条。它不是等所有步数走完再统计,而是每一步都可能产生答案。
我第一次看这题,第一反应也会想递归:
dfs(row, col, rest)
但这地方我一般会多看一眼数据范围。只要步数稍微大一点,递归分叉就是 4^maxMove,缓存不加基本没法看。加缓存当然能过,但这题用动态规划写起来更稳,也更像线上那种“按轮次滚状态”的处理方式。
状态不用搞复杂。
dp[r][c] 表示当前这一步之前,球在 (r, c) 的路径数。
每走一步,就从当前所有格子往四个方向扩散:
如果新位置还在网格里,累加到下一轮; 如果新位置出界,答案加上当前格子的路径数。
这就完了。
关键代码如下:
classSolution:
deffindPaths(
self,
m: int,
n: int,
maxMove: int,
startRow: int,
startColumn: int
) -> int:
mod = 1_000_000_007
cur = [[0] * n for _ in range(m)]
cur[startRow][startColumn] = 1
ans = 0
dirs = ((1, 0), (-1, 0), (0, 1), (0, -1))
for _ in range(maxMove):
nxt = [[0] * n for _ in range(m)]
for r in range(m):
for c in range(n):
ways = cur[r][c]
if ways == 0:
continue
for dr, dc in dirs:
nr, nc = r + dr, c + dc
if nr < 0or nr >= m or nc < 0or nc >= n:
ans = (ans + ways) % mod
else:
nxt[nr][nc] = (nxt[nr][nc] + ways) % mod
cur = nxt
return ans
这段代码里有个细节我会保留:
if ways == 0:
continue
不是为了显得高级,就是少跑点空格子。很多时候 DP 表一开始很稀,尤其 maxMove 不大的时候,大量格子根本到不了,硬扫四个方向没必要。
再拿一个小例子过一下。
m = 2
n = 2
maxMove = 2
startRow = 0
startColumn = 0
第一步,从左上角出发:
往上出界,答案 +1。 往左出界,答案 +1。 往下到 (1,0)。 往右到 (0,1)。
第二步,球可能在 (1,0) 和 (0,1)。
从 (1,0) 往下出界,往左出界,各加 1。 从 (0,1) 往上出界,往右出界,各加 1。
最后答案就是 6。
这题还有一个坑,很多人会把 maxMove 理解成“必须走满这么多步”。其实不是,题目说的是最多走这么多步。第一步出界就已经算了,后面不用管这条路径了。
所以答案必须在每一轮扩散时累加,而不是等循环结束以后再看哪些点在边界上。
边界点也不是答案。 从边界点走出去的那一步,才是答案。
时间复杂度是:
O(maxMove * m * n * 4)
四个方向可以当常数看,实际就是 maxMove * m * n。
空间复杂度是:
O(m * n)
因为只保留当前轮和下一轮,不需要把所有步数的状态都存下来。
这题写 DFS + 记忆化也可以,但 DP 的好处是顺序很清楚:一轮一轮推,路径什么时候出界,答案什么时候增加,不容易把“最多走几步”和“正好走几步”搞混。
这种题别急着套模板,先盯住一句话:出界发生在移动那一刻。 代码就顺了。