题目描述
给一个 n * m 的棋盘,每个街口有个红绿灯周期 T。车辆通过每段街道需要 timePerRoad 时间。
在街口想直行或左转,得等一个完整的红灯周期 T;右转不用等。起点和终点不算红绿灯时间,可以任意方向通过。
求从起点 (rowStart, colStart) 到终点 (rowEnd, colEnd) 的最短时间。如果走不通,返回 -1。
n 和 m 都不超过 9,timePerRoad 最大 600,红绿灯周期最大 120。
讲个故事:小明的导航失灵了
小明开车去赴约,结果导航抽风,只告诉他每个路口红灯多久。他发现:
- 直走或左转,得乖乖等红灯
- 右转美滋滋,不用等
- 起点和终点随便走,没人管
这不就是现实版"能右拐绝不左转"吗?
核心原理:为什么不是普通 BFS?
如果只是找最短路径,BFS 就行。但每个路口的"等待时间"不一样,而且能不能走取决于你从哪个方向来、往哪个方向去。
所以普通坐标不够,得把状态扩成三元组:(行, 列, 朝向)。朝向表示你到达这个路口时面朝哪个方向。
从状态 (r, c, dir) 出发,下一步有三种选择:
- 直行:继续朝
dir走,要等待 - 左转:朝左边走,要等待
- 右转:朝右边走,不用等待
起点可以任意朝向出发,且不用等。到达终点直接结束,也不用等。
这就是带状态的 Dijkstra。
怎么实现?
- 定义四个方向:上、右、下、左
- 左转 =
(dir + 3) % 4,右转 =(dir + 1) % 4,直行 =dir - 状态
(r, c, dir)的代价是到达(r, c)且面朝dir的最短时间 - 用优先队列跑 Dijkstra
- 第一次弹出终点坐标时,就是答案
代码实现
C 语言
c
#include <stdio.h>
#include <string.h>
#define INF 0x3f3f3f3f
int calcTime(int lights[9][9], int n, int m, int timePerRoad,
int rs, int cs, int re, int ce) {
int dr[4] = {-1, 0, 1, 0};
int dc[4] = {0, 1, 0, -1};
int dist[9][9][4];
int used[9][9][4] = {0};
memset(dist, 0x3f, sizeof(dist));
for (int d = 0; d < 4; d++) dist[rs][cs][d] = 0;
while (1) {
int mr = -1, mc = -1, md = -1, minCost = INF;
for (int r = 0; r < n; r++)
for (int c = 0; c < m; c++)
for (int d = 0; d < 4; d++)
if (!used[r][c][d] && dist[r][c][d] < minCost) {
minCost = dist[r][c][d];
mr = r; mc = c; md = d;
}
if (mr == -1) break;
if (mr == re && mc == ce) return minCost;
used[mr][mc][md] = 1;
for (int turn = 0; turn < 3; turn++) {
int ndir = md;
if (turn == 1) ndir = (md + 3) % 4; // 左转
else if (turn == 2) ndir = (md + 1) % 4; // 右转
int nr = mr + dr[ndir], nc = mc + dc[ndir];
if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue;
int wait = 0;
if (!(mr == rs && mc == cs)) {
if (ndir == md || ndir == (md + 3) % 4)
wait = lights[mr][mc];
}
int ncost = minCost + wait + timePerRoad;
if (ncost < dist[nr][nc][ndir])
dist[nr][nc][ndir] = ncost;
}
}
int ans = INF;
for (int d = 0; d < 4; d++)
ans = ans < dist[re][ce][d] ? ans : dist[re][ce][d];
return ans == INF ? -1 : ans;
}
C++
cpp
#include <bits/stdc++.h>
using namespace std;
int calcTime(vector<vector<int>> lights, int timePerRoad,
int rs, int cs, int re, int ce) {
int n = lights.size(), m = lights[0].size();
int dr[4] = {-1, 0, 1, 0};
int dc[4] = {0, 1, 0, -1};
const int INF = 0x3f3f3f3f;
vector<vector<array<int, 4>>> dist(n, vector<array<int, 4>>(m, {INF, INF, INF, INF}));
priority_queue<pair<int, array<int, 3>>, vector<pair<int, array<int, 3>>>, greater<>> pq;
for (int d = 0; d < 4; d++) {
dist[rs][cs][d] = 0;
pq.push({0, {rs, cs, d}});
}
while (!pq.empty()) {
auto [cost, state] = pq.top(); pq.pop();
auto [r, c, dir] = state;
if (r == re && c == ce) return cost;
if (cost > dist[r][c][dir]) continue;
for (int turn = 0; turn < 3; turn++) {
int ndir = dir;
if (turn == 1) ndir = (dir + 3) % 4;
else if (turn == 2) ndir = (dir + 1) % 4;
int nr = r + dr[ndir], nc = c + dc[ndir];
if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue;
int wait = 0;
if (!(r == rs && c == cs)) {
if (ndir == dir || ndir == (dir + 3) % 4)
wait = lights[r][c];
}
int ncost = cost + wait + timePerRoad;
if (ncost < dist[nr][nc][ndir]) {
dist[nr][nc][ndir] = ncost;
pq.push({ncost, {nr, nc, ndir}});
}
}
}
int ans = INF;
for (int d = 0; d < 4; d++) ans = min(ans, dist[re][ce][d]);
return ans == INF ? -1 : ans;
}
Java
java
import java.util.PriorityQueue;
public class Main {
static int INF = 0x3f3f3f3f;
static int[] dr = {-1, 0, 1, 0};
static int[] dc = {0, 1, 0, -1};
static class Node implements Comparable<Node> {
int r, c, dir, cost;
Node(int r, int c, int dir, int cost) {
this.r = r; this.c = c; this.dir = dir; this.cost = cost;
}
public int compareTo(Node o) {
return this.cost - o.cost;
}
}
public static int calcTime(int[][] lights, int timePerRoad,
int rs, int cs, int re, int ce) {
int n = lights.length, m = lights[0].length;
int[][][] dist = new int[n][m][4];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
for (int k = 0; k < 4; k++)
dist[i][j][k] = INF;
PriorityQueue<Node> pq = new PriorityQueue<>();
for (int d = 0; d < 4; d++) {
dist[rs][cs][d] = 0;
pq.offer(new Node(rs, cs, d, 0));
}
while (!pq.isEmpty()) {
Node cur = pq.poll();
if (cur.r == re && cur.c == ce) return cur.cost;
if (cur.cost > dist[cur.r][cur.c][cur.dir]) continue;
for (int turn = 0; turn < 3; turn++) {
int ndir = cur.dir;
if (turn == 1) ndir = (cur.dir + 3) % 4;
else if (turn == 2) ndir = (cur.dir + 1) % 4;
int nr = cur.r + dr[ndir], nc = cur.c + dc[ndir];
if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue;
int wait = 0;
if (!(cur.r == rs && cur.c == cs)) {
if (ndir == cur.dir || ndir == (cur.dir + 3) % 4)
wait = lights[cur.r][cur.c];
}
int ncost = cur.cost + wait + timePerRoad;
if (ncost < dist[nr][nc][ndir]) {
dist[nr][nc][ndir] = ncost;
pq.offer(new Node(nr, nc, ndir, ncost));
}
}
}
int ans = INF;
for (int d = 0; d < 4; d++) ans = Math.min(ans, dist[re][ce][d]);
return ans == INF ? -1 : ans;
}
}
JavaScript
javascript
function calcTime(lights, timePerRoad, rs, cs, re, ce) {
const n = lights.length, m = lights[0].length;
const dr = [-1, 0, 1, 0];
const dc = [0, 1, 0, -1];
const INF = 0x3f3f3f3f;
const dist = Array.from({length: n}, () =>
Array.from({length: m}, () => new Array(4).fill(INF)));
const pq = [];
const push = (node) => {
pq.push(node);
pq.sort((a, b) => a.cost - b.cost);
};
for (let d = 0; d < 4; d++) {
dist[rs][cs][d] = 0;
push({r: rs, c: cs, dir: d, cost: 0});
}
while (pq.length > 0) {
const cur = pq.shift();
if (cur.r === re && cur.c === ce) return cur.cost;
if (cur.cost > dist[cur.r][cur.c][cur.dir]) continue;
for (let turn = 0; turn < 3; turn++) {
let ndir = cur.dir;
if (turn === 1) ndir = (cur.dir + 3) % 4;
else if (turn === 2) ndir = (cur.dir + 1) % 4;
const nr = cur.r + dr[ndir], nc = cur.c + dc[ndir];
if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue;
let wait = 0;
if (!(cur.r === rs && cur.c === cs)) {
if (ndir === cur.dir || ndir === (cur.dir + 3) % 4)
wait = lights[cur.r][cur.c];
}
const ncost = cur.cost + wait + timePerRoad;
if (ncost < dist[nr][nc][ndir]) {
dist[nr][nc][ndir] = ncost;
push({r: nr, c: nc, dir: ndir, cost: ncost});
}
}
}
let ans = INF;
for (let d = 0; d < 4; d++) ans = Math.min(ans, dist[re][ce][d]);
return ans === INF ? -1 : ans;
}
Python
python
import heapq
def calc_time(lights, time_per_road, rs, cs, re, ce):
n, m = len(lights), len(lights[0])
dr = [-1, 0, 1, 0]
dc = [0, 1, 0, -1]
INF = 0x3f3f3f3f
dist = [[[INF] * 4 for _ in range(m)] for _ in range(n)]
pq = []
for d in range(4):
dist[rs][cs][d] = 0
heapq.heappush(pq, (0, rs, cs, d))
while pq:
cost, r, c, dir = heapq.heappop(pq)
if r == re and c == ce:
return cost
if cost > dist[r][c][dir]:
continue
for turn in range(3):
ndir = dir
if turn == 1:
ndir = (dir + 3) % 4
elif turn == 2:
ndir = (dir + 1) % 4
nr, nc = r + dr[ndir], c + dc[ndir]
if not (0 <= nr < n and 0 <= nc < m):
continue
wait = 0
if not (r == rs and c == cs):
if ndir == dir or ndir == (dir + 3) % 4:
wait = lights[r][c]
ncost = cost + wait + time_per_road
if ncost < dist[nr][nc][ndir]:
dist[nr][nc][ndir] = ncost
heapq.heappush(pq, (ncost, nr, nc, ndir))
ans = min(dist[re][ce])
return -1 if ans == INF else ans
复杂度分析
- 状态数:
n * m * 4,最大9 * 9 * 4 = 324 - 时间复杂度:
O(n * m * 4 * log(n * m * 4)),约等于O(n * m * log(n * m)) - 空间复杂度:
O(n * m * 4),存距离数组和优先队列
总结一下
这道题的关键是:路口状态不能只看坐标,得加上朝向。
把 (r, c, dir) 当成图上的一个点,用 Dijkstra 跑一遍,右转免费、直行左转等红灯的规则自然就被编码进去了。
下次遇到"路口""红绿灯""转向限制"这种关键词,先把状态升维,大概率就是正解。
你遇到过哪些看起来很复杂、其实升维一下就解决的题?欢迎在评论区聊聊。