题解/算法 {LCP 79. 提取咒文}
LINK: https://leetcode.cn/problems/kjpLFZ/
;
首先一定要读懂题目…
.
比赛时, 莫名其妙的以为: 比如字符串为abcd
, 那么: 起点-a
的最短距离 + a-b
的最短距离 + b-c
的最短距离 + c-d
的最短距离, 就是答案… 这当然是错误的, 题目都没读懂, 人家是要连续移动的, 而你认为所有相同的字符之间 移动是无代价的, 这当然都是错误的…
最重要的是要知道, 他是在移动 即从起点开始 上下左右的移动, 每次移动 都是有代价的; 因此, 答案 一定是一条路径 即(起点)-> ... -> 终点
(权值为: 移动次数 + 字符串长度), 看到这一点 非常重要;
-{
错误思路; (但可以投机取巧AC);
由于是要取这个字符串, 我们的关注点 很容易把重点去关注这个字符串, 即先去取S[0]
, 然后去取S[1]
, … 这当然是正确的, 于是你得到了下面的模型:
.
令Set( S[i])
表示 该二维矩阵里 所有值为S[i]
的坐标, 得到一个DAG图: (起点) -> Set( S[0]) -> Set( S[1]) ...
(比如Set( S[i]) -> Set( S[i+1])
表示: S[i]
里的每个元素 向S[i+1]
的每个元素 连接一条 abs(x1-x2) + abs(y1-y2)
的边, 即连接|S[i]| * |S[i+1]|
条边;
每个点是由(x,y,ind)
三个维度组成, 表示 S[ind]
里有个(x,y)
的坐标元素;
答案肯定是: 起点 -> Set( S[0])的某个点 -> Set( S[1])的某个点 -> ...
;
也就是, 在这个图上 跑最短路, 由于他是DAG 可以使用DP递推 或 DFS记忆化 (两者一样), 重点是考虑下他是时间复杂度;
时间是: O(N * K)
(N是点数, K是每个点的邻接点个数);
想象一个特例, S = "a...a"
且二维网格里全都是a
, 此时 点数有1e4 * 1e2
个, 每个点 有1e4
个临界点, 这一定是超时的; (不管用什么算法, 最短路/DP递推, 都是针对的是这个图, 而这个图本身 就是超时的 因为他有1e10
条边);
.
不要想着把这个特例给特判掉, 这就心术不正了, 比如S="abababab..."
二维网格里一半的a
一半的b
, 时间也是一样的;
比赛时使用的是DFS记忆化 (其实直接用DP递推即可), 然后用一个counter
计数 由于邻接点很多 就枚举counter
个即可…
碰巧给AC了, 这是错误的做法 只是后台数据不强, 因为不管你什么算法 都是对这个图跑最短路, 而这个图 本身就是超时的 边数太多了;
.
因为是两个集合间 完全映射 共N*M
条边, 如果可以用虚拟节点 则可以优化为N+M
, 但由于这里 这N*M
条边 权值都不一样, 没办法优化;
-}
如果就专注于这个图 想着如何在这个图上跑最短路, 注定是无解的, 因为这个图 本身就是超时的… 必须要换个思路 (很难做到, 因为你可能已经陷入那个图里去了…);
你忘记了 上下左右移动 这个操作, 上面分析过 答案对应在二维网格里 一定是一条连续的路径 (即通过上下左右 一步一步的移动), 你之前错误的做法 没有用到移动 而是直接跳过去的 (即从(x,y)
直接跳到 (x1,y1)
);
即, 我们此时 要从一个点 往他4个方向去移动, 对于一个点(x,y)
他可能会多次经过 目的是为了取字符 比如S="abacadae...
假如网格只有一个点为a
的话 那么这点 会多次访问, 但虽然多次访问 但每次访问 他对应的S
的下标 是不同的;
因此, 使用(x,y,ind)
来表示一个节点: 当前在(x,y)
位置 且已经拿到了S[0,1,...,ind)
这个子串 的最短路;
同样需要建图, 所谓建图 即节点与节点之间的连线 要怎么连;
对于当前(x,y,ind)
:
.
如果G[x][y] == S[ind]
, 则拿当前位置上的字符 即转移到(x,y, ind+1)
, 代价为1;
.
向4个方向移动 即转移到(dx, dy, ind)
, 代价也是1;
因此, 这是个BFS-1的模型; (注意, 对于第二种情况 即往4个方向移动, 你不要再去判断 如果G[ dx][ dy] == S[ind]
则就更新(dx, dy, ind+1)
, 不要这样做 因为这样的话 他的代价是2, 就不是BFS-1的模型了;
也就是, 一个状态 有5个临界点; 虽然和之前一样 节点都是1e6
个, 但边数不同了;