如何解决地窖房间的最短时间问题:从问题分析到代码实现

问题描述

我们有一个n×m的地窖房间网格,每个房间有一个特定的"可进入时间"moveTime[i][j]。我们从左上角(0,0)出发,每次可以移动到相邻的房间(上下左右)。移动时间的规则很特别:第一次移动花费1秒,第二次2秒,第三次1秒,第四次2秒...如此交替。我们的目标是找到到达右下角(n-1,m-1)的最短时间。

问题分析

这个问题结合了图论中的最短路径问题和特殊的移动时间规则。我们需要考虑:

  1. 网格结构:房间形成二维网格,每个房间有四个可能的移动方向

  2. 移动时间交替:1秒、2秒、1秒、2秒...

  3. 房间进入限制:必须在moveTime[i][j]之后才能进入该房间

解决思路

这类问题通常可以使用Dijkstra算法来解决,因为:

  1. 我们需要找到最短时间路径

  2. 边的"权重"(移动时间)是动态变化的

  3. 有额外的进入时间限制

逐步实现

第一步:数据结构准备

我们需要表示每个房间的状态,包括:

  • 坐标(x,y)

  • 到达该房间的时间

  • 移动次数(用于确定下一次移动的时间)

java

class State implements Comparable {
    int x, y, dis;
    State(int x, int y, int dis) {
        this.x = x;
        this.y = y;
        this.dis = dis;
    }
    @Override
    public int compareTo(State other) {
        return Integer.compare(this.dis, other.dis);
    }
}

第二步:初始化

设置初始状态和必要的辅助数据结构:

java

int n = moveTime.length;
int m = moveTime[0].length;
int[][] d = new int[n][m]; // 存储到达每个房间的最短时间
boolean[][] v = new boolean[n][m]; // 标记是否已访问

// 初始化距离为无穷大
for (int i = 0; i < n; i++) {
    Arrays.fill(d[i], INF);
}

d[0][0] = 0; // 起点时间为0
PriorityQueue q = new PriorityQueue<>();
q.offer(new State(0, 0, 0));

第三步:Dijkstra算法主循环

处理优先队列中的状态:

java

while (!q.isEmpty()) {
    State s = q.poll();
    if (v[s.x][s.y]) continue;
    if (s.x == n-1 && s.y == m-1) break; // 到达终点
    
    v[s.x][s.y] = true;
    
    // 四个移动方向
    int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    for (int[] dir : dirs) {
        int nx = s.x + dir[0];
        int ny = s.y + dir[1];
        
        // 检查边界
        if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
        
        // 计算移动时间:交替1和2秒
        int moveCost = (s.x + s.y) % 2 + 1;
        // 到达新房间的时间 = max(当前时间+移动时间, 房间的进入时间)
        int dist = Math.max(d[s.x][s.y] + moveCost, moveTime[nx][ny]);
        
        // 如果找到更短路径,更新并加入队列
        if (d[nx][ny] > dist) {
            d[nx][ny] = dist;
            q.offer(new State(nx, ny, dist));
        }
    }
}

第四步:返回结果

最终,右下角房间的最短时间存储在d[n-1][m-1]中:

java

return d[n-1][m-1];

完整代码

java

import java.util.PriorityQueue;
import java.util.Arrays;

public class Solution {
    private static final int INF = 0x3f3f3f3f;

    class State implements Comparable {
        int x, y, dis;
        State(int x, int y, int dis) {
            this.x = x;
            this.y = y;
            this.dis = dis;
        }
        @Override
        public int compareTo(State other) {
            return Integer.compare(this.dis, other.dis);
        }
    }

    public int minTimeToReach(int[][] moveTime) {
        int n = moveTime.length;
        if (n == 0) return 0;
        int m = moveTime[0].length;
        if (m == 0) return 0;

        int[][] d = new int[n][m];
        boolean[][] v = new boolean[n][m];
        for (int i = 0; i < n; i++) {
            Arrays.fill(d[i], INF);
        }

        int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
        d[0][0] = 0;
        PriorityQueue q = new PriorityQueue<>();
        q.offer(new State(0, 0, 0));
        while (!q.isEmpty()) {
            State s = q.poll();
            if (v[s.x][s.y]) continue;
            if (s.x == n-1 && s.y == m-1) break;
            v[s.x][s.y] = true;
            
            for (int[] dir : dirs) {
                int nx = s.x + dir[0];
                int ny = s.y + dir[1];
                if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
                int dist = Math.max(d[s.x][s.y] + ((s.x + s.y) % 2 + 1), moveTime[nx][ny]);
                if (d[nx][ny] > dist) {
                    d[nx][ny] = dist;
                    q.offer(new State(nx, ny, dist));
                }
            }
        }
        return d[n-1][m-1];
    }
}

关键点解析

  1. 移动时间计算:利用坐标和(x+y)%2巧妙地实现了1秒和2秒的交替

  2. 房间进入时间处理:使用Math.max确保满足房间的进入时间要求

  3. 优先队列:确保总是处理当前已知的最短路径

  4. 访问标记:避免重复处理已经找到最优解的房间

复杂度分析

  • 时间复杂度:O(nm log(nm)),因为每个房间最多被处理一次,优先队列操作是log级别的

  • 空间复杂度:O(nm),用于存储距离和访问标记

总结

这个问题展示了如何将Dijkstra算法应用于有特殊规则的网格路径问题。关键在于:

  1. 正确建模移动时间的交替规则

  2. 处理房间的进入时间限制

  3. 高效地实现优先队列处理

通过逐步分析和实现,我们能够有效地解决这个看似复杂的问题。

你可能感兴趣的:(算法,leetcode,数据结构)