BM61 矩阵最长递增路径¶
中等通过率 33.1%python3核心代码模式样例通过牛客 AC图动态规划dfs
讲解章节:记忆化搜索与剪枝
一句话
按数值从大到小推 dp,dp[i][j] = 1 + 更大邻居里的最大 dp。
解题思路¶
这题考什么¶
设 dp[i][j] 为「从 (i,j) 出发」能走出的最长递增路径长度,答案就是所有 dp 的最大值。 转移显然:
因为路径必须严格递增,从 (i,j) 走出去之后不可能再绕回来(值只增不减), 所以这张图天然无环,dp 有良定义,也不必再记 visited。
朴素写法是记忆化搜索,但矩阵可到 10001000, 一条蛇形递增的路径能让递归深到 1e6 层,Python 直接爆栈。 这里改成按拓扑序迭代*,而这题的拓扑序不用另算——就是数值从大到小: 处理到值为 v 的格子时,它所有「值 > v」的邻居都已经算完了; 同一桶内的格子值相等、彼此之间没有边,先后顺序随意。
1 2 3 按值降序处理 9,8,7,6,5,4,3,2,1
4 5 6 dp[9]=1, dp[6]=2, dp[3]=3, dp[2]=4, dp[1]=5 -> 1->2->3->6->9
7 8 9
DAG 上按拓扑序递推的一般写法见 拓扑排序。
数据规模与复杂度¶
1 <= n,m <= 1000(最多 1e6 个格子),0 <= matrix[i][j] <= 1000(值域 1001 档), 时限「C/C++ 3 秒,其他语言 6 秒」——比这套题单常见的 2 秒宽,正是因为规模大。 分桶 O(nm + V)、主循环每格看四个邻居 O(4nm),合计 O(nm + V),对上题面进阶要求; 空间 O(nm)(dp 表加桶)。 改用排序求拓扑序是 O(nm log nm),1e6 * 20 = 2e7 次比较,Python 上明显更慢; 值域只有 1001 档时桶排是白捡的便宜。
坑在哪¶
- 记忆化搜索在这题上不是等价选项:1000*1000 的蛇形递增矩阵能让递归深度到 1e6, 远超 Python 默认的 1000 层上限,而调高 setrecursionlimit 又会撞爆 C 栈。 迭代按拓扑序推 dp 才稳。
- 拓扑序为什么就是数值降序:dp[i][j] 只依赖值严格大于它的邻居, 所以轮到值 v 时,一切可能被引用的格子都在更早的桶里定稿了,一遍扫过去无需回头。
- 邻居判定必须是严格大于。写成 >= 会在相等的相邻格之间造出双向边, 图不再无环,dp 的定义随之失效(互相引用,谁也算不出来)。
- dp 初值取 1 而不是 0:单个格子本身就是一条长度为 1 的路径, 题面示例 2 的 [[1,2],[4,3]] 答案是 3,全 0 起步会整体差 1。
- 桶下标要减去最小值做偏移(buckets[v - lo]):矩阵值域虽从 0 起, 但按实际出现的最小值开桶更省内存,代价是取用时必须记得同一套偏移。
- 答案初值取 1 而非 0:题面保证 n,m >= 1,矩阵非空时最短路径也有 1 格。
样例复核¶
[[1,2,3],[4,5,6],[7,8,9]]:9 的四邻没有更大的,dp=1;6 的邻居 9 更大,dp=2; 3 的邻居 6 更大,dp=3;2 -> 3 得 dp=4;1 -> 2 得 dp=5。最大值 5,与示例 1 一致。
参考实现¶
[:octicons-arrow-left-16: BM60](BM60.md) [BM62 :octicons-arrow-right-16:](BM62.md)