构造字符串的最短路径问题:矩阵按序遍历符号的最短步数求解
求解矩阵中按指定符号序列遍历的最短路径步数
问题概述
给定一个N×M的符号矩阵,以及一个指定的符号序列,我们需要找到按顺序遍历序列中所有符号的最短路径步数。允许的移动方向为上下左右四个方向,同一个符号可以在矩阵中多次出现,因此常规的图遍历算法(比如单纯的BFS或Dijkstra)并不适用,而动态规划是更高效的解决方案。
举个实际例子:
矩阵为:
1 2 3 2 6 5 3 4 6
指定序列为 1→2→3→4→5→6→1,最终最短路径步数为8(路径:D、D、R、R、U、L、U、L)。
核心思路:动态规划
因为我们需要按顺序遍历序列,且每个符号可能对应多个位置,动态规划的核心是记录遍历到序列第i个符号时,在该符号所有可能位置上的最短步数,然后基于前一步的状态推导当前状态。
1. 预处理:映射符号到位置
首先遍历整个矩阵,建立一个哈希表,把每个符号映射到它在矩阵中所有出现的位置坐标列表。比如上面的例子中,符号2对应的位置是[(0,1), (1,0)]。
2. 状态定义
我们用两个字典来简化状态存储:
prev_dp:存储遍历到序列第i-1个符号时,该符号所有对应位置的最短步数。键是位置坐标(x,y),值是到达该位置的最短步数。curr_dp:存储遍历到序列第i个符号时,该符号所有对应位置的最短步数,基于prev_dp计算得到。
3. 初始化状态
对于序列的第一个符号,所有对应的位置的初始步数都是0(因为我们从这些位置开始,还没有移动)。
4. 状态转移过程
从序列的第二个符号开始,依次处理每个符号:
- 初始化
curr_dp的所有值为无穷大(表示初始不可达)。 - 遍历上一个符号的所有位置
(x_prev, y_prev),以及当前符号的所有位置(x_curr, y_curr):- 计算两个位置之间的曼哈顿距离(上下左右移动的最短步数就是曼哈顿距离):
d = |x_curr - x_prev| + |y_curr - y_prev| - 更新
curr_dp[(x_curr, y_curr)]为当前值和prev_dp[(x_prev, y_prev)] + d中的较小值。
- 计算两个位置之间的曼哈顿距离(上下左右移动的最短步数就是曼哈顿距离):
- 把
prev_dp替换为curr_dp,进入下一个符号的处理。
5. 最终结果
处理完序列的最后一个符号后,prev_dp中所有值的最小值就是我们要找的最短路径步数。
示例验证
用开头的例子走一遍流程:
序列:
1→2→3→4→5→6→1预处理符号位置:
1: [(0,0)]2: [(0,1), (1,0)]3: [(0,2), (2,0)]4: [(2,1)]5: [(1,2)]6: [(1,1), (2,2)]
初始化:
prev_dp = {(0,0): 0}处理第二个符号
2:- 计算从(0,0)到(0,1)的距离是1 →
curr_dp[(0,1)] = 1 - 计算从(0,0)到(1,0)的距离是1 →
curr_dp[(1,0)] = 1 prev_dp更新为{(0,1):1, (1,0):1}
- 计算从(0,0)到(0,1)的距离是1 →
处理第三个符号
3:- 对(0,2):从(0,1)来是1+1=2,从(1,0)来是1+3=4 → 取2
- 对(2,0):从(0,1)来是1+3=4,从(1,0)来是1+1=2 → 取2
prev_dp更新为{(0,2):2, (2,0):2}
处理第四个符号
4:- 位置(2,1):从(0,2)来是2+3=5,从(2,0)来是2+1=3 → 取3
prev_dp更新为{(2,1):3}
处理第五个符号
5:- 位置(1,2):从(2,1)来是3+2=5 →
prev_dp更新为{(1,2):5}
- 位置(1,2):从(2,1)来是3+2=5 →
处理第六个符号
6:- 位置(1,1):从(1,2)来是5+1=6
- 位置(2,2):从(1,2)来是5+2=7
prev_dp更新为{(1,1):6, (2,2):7}
处理第七个符号
1:- 位置(0,0):从(1,1)来是6+2=8,从(2,2)来是7+4=11 → 取8
- 最终最小值就是8,和示例输出一致。
优化点
- 空间优化:不需要保存整个序列的所有状态,只需要保存前一步的状态即可,这样空间复杂度从O(kNM)降到O(N*M)(k是序列长度)。
- 提前终止:如果某一步的
curr_dp全是无穷大,说明没有可行路径,可以直接返回-1(或提示无解)。
内容的提问来源于stack exchange,提问作者Pal Jereh
相关产品推荐
相关产品推荐

