力扣刷题之815.公交路线

题干描述

给你一个数组 routes ,表示一系列公交线路,其中每个 routes[i] 表示一条公交线路,第 i 辆公交车将会在上面循环行驶。

  • 例如,路线 routes[0] = [1, 5, 7] 表示第 0 辆公交车会一直按序列 1 -> 5 -> 7 -> 1 -> 5 -> 7 -> 1 -> ... 这样的车站路线行驶。

现在从 source 车站出发(初始时不在公交车上),要前往 target 车站。 期间仅可乘坐公交车。

求出 最少乘坐的公交车数量 。如果不可能到达终点车站,返回 -1 。

示例 1:

输入:routes = [[1,2,7],[3,6,7]], source = 1, target = 6
输出:2
解释:最优策略是先乘坐第一辆公交车到达车站 7 , 然后换乘第二辆公交车到车站 6 。 

示例 2:

输入:routes = [[7,12],[4,5,15],[6],[15,19],[9,12,13]], source = 15, target = 12
输出:-1

题干分析

问题描述

       已知我们有这一系列的公交线路,以及routes数组,其中routes[i]表示dii来你个公交所进过的车站列表。每辆公交车按照起路线循环行驶。

  • 目标:从起始站点source出发,前往目标站点target,且进通过乘坐公交车。
  • 要求:找到从source到target需要乘坐的最少公交车数量。
  • 注意:如果无法到达目标站点,返回-1。

 示例:

1.

输入:routes = [[1,2,7],[3,6,7]], source = 1, target = 6
输出:2
解释:乘坐公交车 0 从站点 1 到站点 7,然后换乘公交车 1 到站点 6。

 2.

输入:routes = [[7,12],[4,5,15],[6],[15,19],[9,12,13]], source = 15, target = 12
输出:-1
解释:无法从站点 15 到达站点 12。

解题思路

        这个问题可以抽象为图的最短路径问题。我们需要构建一个公交车之间的图,然后使用官渡有限搜索(BFS)来找到从起始站点到目标站的最短路径。

  • 节点(顶点):公交车(每辆公交车做一个节点)。
  • 边(连接):若两辆公交车之间存在公共的站点,则它们之间可以换乘,形成一条边。
  • 起始节点:所有进过source站点的公交车。
  • 目标节点:所有经过target站点的公交车。
  • 权重:每次换乘所一步,所以没条边的权重为1。

 这里我们以示例2为例,大概如下:

 步骤概述

1.建立站点到公交车的映射:
  • 为每个站点创建一个列表,记录经过agic站点的所有公交车。
2.构建公交车之间的连接
  • 对于没对公交车,如果它们有公共的站点,则它们之间可以直接换乘。
3.使用BFS进行搜索:
  • 从所有经过source站点的公交车开始搜索。
  • 逐层遍历可以换乘的公交车,记录乘坐的公交车数量。
  • 如果某一辆公交车经过target站点,则返回已乘坐的公交车数量。
4.处理无法到达的情况:
  • 如果在搜索结束后,仍未找到经过target站点的公交车,则返回-1。

代码讲解 

#define MAX_BUS_STOPS 1000000
#define MAX_BUSES 500

typedef struct {
    int bus_id;
    int next;
} BusNode;

typedef struct {
    int head;
} BusStop;

typedef struct {
    int bus_id;
    int depth;
} QueueNode;
第一步:定义数据结构
定义结构体BusNode:用于存储经过某个站点的公交车的链表节点,其中:
  • bus_id:用于定义公交车的编号。
  • next:用于指向下一个BusNode的索引。
定义结构体BusStop:表示一个公交站点,其中:
  • head:指向该站点对应的公交车链表的头节点索引。
定义结构体QueueNode:用于BFS队列,表示一个代访问的公交车节点。
  • bus_id:公交车的编号。
  • depth:到达该公交车所需乘坐的公交车数量。
第二步 特殊情况考虑
  • 如果source等于target,则无需乘坐公交车,直接返回0。
int numBusesToDestination(int** routes, int routesSize, int* routesColSize, int source, int target) {
    if (source == target) return 0;
 第三步 初始化站点到公交车的映射
  • 创建一个大小为 MAX_BUS_STOPS 的数组 busStops,用于存储每个站点对应的公交车链表。
  • 初始化每个站点的 head-1,表示链表为空。
    // 创建站点到公交车的映射
    BusStop* busStops = (BusStop*)malloc(sizeof(BusStop) * MAX_BUS_STOPS);
    for (int i = 0; i < MAX_BUS_STOPS; i++) {
        busStops[i].head = -1;
    }
 第四步  初始化公交车链表数组
  • 创建一个数组busNodes,用于存储所有的BusNode节点
  • busNodeCount用于记录已经使用的节点数量。     
    for (int bus_id = 0; bus_id < routesSize; bus_id++) {
        for (int j = 0; j < routesColSize[bus_id]; j++) {
            int bus_stop_id = routes[bus_id][j];
            BusNode* node = &busNodes[busNodeCount++];
            node->bus_id = bus_id;
            node->next = busStops[bus_stop_id].head;
            busStops[bus_stop_id].head = busNodeCount - 1;
        }
    }
 第五步 建立站点到公交车的映射
  • 遍历没来那个公交车的路线,将公交车编号添加到经过的每个站点的链表中。
  • 对于每个站点,新的BusNode节点插入到链表头部。实现了链表的建立。
    // BFS 初始化
    int visitedBuses[MAX_BUSES];
    memset(visitedBuses, 0, sizeof(visitedBuses));

    int queueSize = MAX_BUSES * 1000;
    QueueNode* queue = (QueueNode*)malloc(sizeof(QueueNode) * queueSize);
    int front = 0, rear = 0;
第六步 初始化 BFS所需的数据结构:
  • visiteBuses:记录已经访问过的公交车,防止重复访问。
  • queue:BFS队列,用于存储代访问的公交车。
  • front和rear:队列的头尾指针。
    // 将经过起始站点的公交车加入队列
    for (int idx = busStops[source].head; idx != -1; idx = busNodes[idx].next) {
        int bus_id = busNodes[idx].bus_id;
        if (!visitedBuses[bus_id]) {
            visitedBuses[bus_id] = 1;
            queue[rear++] = (QueueNode){bus_id, 1};
        }
    }
 第七步 将经过source站点的公交车加入队列
  • 遍历source站点的公交车链表,将每个公交车编号加入队列,初始乘坐公交车数量为1。
  • 标记这些公交车为已访问。
    // BFS 遍历
    while (front < rear) {
        QueueNode current = queue[front++];
        int bus_id = current.bus_id;
        int depth = current.depth;

        // 检查该公交车是否经过目标站点
        for (int i = 0; i < routesColSize[bus_id]; i++) {
            int bus_stop_id = routes[bus_id][i];
            if (bus_stop_id == target) {
                free(busStops);
                free(busNodes);
                free(queue);
                return depth;
            }
        }
第八步 开始BFS遍历

        从队列中取出一个公交车节点,湖区当前公交车编号和已乘坐的公交车数量。检查当前公交车是否经过target站点:

  • 遍历该公交车的路线,如果发现target站点,返回当前乘坐的公交车数量depth。
 第九步 遍历当前公交车经过的所有站点
        // 将该公交车经过的所有站点的其他公交车加入队列
        for (int i = 0; i < routesColSize[bus_id]; i++) {
            int bus_stop_id = routes[bus_id][i];
            for (int idx = busStops[bus_stop_id].head; idx != -1; idx = busNodes[idx].next) {
                int next_bus_id = busNodes[idx].bus_id;
                if (!visitedBuses[next_bus_id]) {
                    visitedBuses[next_bus_id] = 1;
                    queue[rear++] = (QueueNode){next_bus_id, depth + 1};
                }
            }
        }
    }

对于当前公交车经过的每个站点

  • 获取经过该站点的所有公交车。
  • 如果这些公交未被访问过,将其加入队列,乘坐的公交车数量+1。
  • 标记这些公交车为已访问
第十步 无法到达目标站点:
  • 如果队列为空,仍未找到经过target站点的公交车,释放动态分配的内存,返回-1.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值