公交路线 815

公交路线 815

Tue Sep 17 2024
10 分钟
1525 字

考虑到未来找实习和工作的需要,至少在心血来潮的时候刷刷LeetCode,顺便进行C++复健,顺便学学其他语言的语法。写下这道题也是在mark一个开始。这个难度的题大概以后也不会都写。。有(看)感(心)悟(情)或者足够变态的题目再写吧。

顺便感叹下DALL-E现在越来越厉害了。前几个博客的图都是生成的。

题目#

给你一个数组 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

提示:#

  • 1 <= routes.length <= 500
  • 1 <= routes[i].length <= 10^5
  • routes[i] 中的所有值 互不相同
  • sum(routes[i].length) <= 10^5
  • 0 <= routes[i][j] < 10^6
  • 0 <= source, target < 10^6

分析与解答#

两年没做算法题目了。既然是复健,我将从直觉开始想。要求的是公交车数量,将两个车站之间的距离定义为公交车数量。循环行驶,说明可以看作是无向图。 这样一来,直接能想到的办法就是BFS。先不考虑优化,写出第一版。基本思路是从车站开始,遍历过这个站的车,更新到达站点的深度。根据BFS的特性,第一次到达站点的深度就是最小的。

基础复盘#

vector#

开始之前,我发现我需要快速过一下vector的用法。其他的STL用到再说。

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // visit
    int first_elem = v[0]; // or v.front();
    int last_elem = v[v.size() - 1]; // or v.back();
    int second_elem = v.at(1);

    // modify
    v.push_back(6);
    v.pop_back();
    v.erase(v.begin() + 2);
    v.erase(v.begin(), v.begin() + 2);
    v.insert(v.begin() + 1, 10); // index 1

    // traverse
    for (int i : v) { // or for (int i = 0; i < v.size(); i++)
        cout << i << " ";
    }

    return 0;
}

unordered_map#

然后反向建图需要一个hash,这里用unordered_map。

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> umap = {{1, "one"}, {2, "two"}, {3, "three"}};

    // insert
    umap.insert({4, "four"});
    umap[5] = "five";

    // visit
    string value = umap[1];
    string value2 = umap.at(2);

    // modify
    umap[1] = "ONE";

    // traverse
    for (auto i : umap) { // or for (auto i = umap.begin(); i != umap.end(); i++)
        cout << i.first << " " << i.second << endl;
    }

    // find 
    auto it = umap.find(1);
    if (it != umap.end()) {
        cout << "Found " << it->first << " " << it->second << endl;
    }

    // delete
    umap.erase(1);
    umap.clear(); // clear all
    cout << umap.size() << umap.empty() << endl;

    return 0;
}

用起来和Python还是很像的。实际复健的时候感觉C++也没那么难。可能是因为当时上课的时候不让用STL,一直没有简单的hash之类的用。。当时还自己写了好多板子:github

queue#

最后是queue,BFS的核心数据结构。就进进出出:

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <queue>
using namespace std;

int main() {
    queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);

    // visit
    int front_elem = q.front();
    int back_elem = q.back();

    // pop
    q.pop();

    // traverse
    while (!q.empty()) {
        cout << q.front() << " ";
        q.pop();
    }

    return 0;
}

第一版代码#

于是,第一版代码:

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
public:
    int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {
        if (source == target) {
            return 0;
        }

        // get station2bus
        unordered_map<int, vector<int>> station2bus;
        for (int i = 0; i < routes.size(); i++) {
            for (int station : routes[i]) {
                station2bus[station].push_back(i);
            }
        }

        unordered_map<int, int> depth;

        // BFS
        queue<int> q;
        q.push(source);
        depth[source] = 0;
        while (!q.empty()) {
            int cur_station = q.front();
            int cur_depth = depth[cur_station];
            q.pop();

            for (int bus : station2bus[cur_station]) {
                for (int station : routes[bus]) {
                    if (station == target) {
                        return cur_depth + 1;
                    }
                    else if(depth.find(station) == depth.end()) {
                        depth[station] = cur_depth + 1;
                        q.push(station);
                    }
                }
            }
        }
        return -1;
    }
};

过了一下测试,发现source target相等的时候不需要先上车再原地下车。。,不过总之这是一个特判。

测试结果是有一个case超时。分析一下时间,记mm辆车,路线长nn。反向建图的时间复杂度是O(mn)O(mn),BFS的时间复杂度是O(mn)O(mn)。所以按理说没问题才对?

仔细检查一下,发现实际上是因为BFS的时候,每次都要遍历一遍所有的车。构造极端情况:所有车首位相连成一条线。那么每个route交点上,都要遍历所有的车,再遍历所有站,时间复杂度就是O(m2n)O(m^2n)。其实车看着不多,容易忽略这个复杂度。

第二版代码#

总之,每个bus只需要访问一次就可以了。在上面的基础上加入一个bus_visit,使用unordered_set,用法简单就不再讲了:

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Solution {
public:
    int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {
        if (source == target) {
            return 0;
        }

        // get station2bus
        unordered_map<int, vector<int>> station2bus;
        for (int i = 0; i < routes.size(); i++) {
            for (int station : routes[i]) {
                station2bus[station].push_back(i);
            }
        }

        unordered_map<int, int> depth;
        unordered_set<int> bus_visit;

        // BFS
        queue<int> q;
        q.push(source);
        depth[source] = 0;
        while (!q.empty()) {
            int cur_station = q.front();
            int cur_depth = depth[cur_station];
            q.pop();

            for (int bus : station2bus[cur_station]) {
                if (bus_visit.find(bus) != bus_visit.end()) {
                    continue;
                }
                bus_visit.insert(bus);
                for (int station : routes[bus]) {
                    if (station == target) {
                        return cur_depth + 1;
                    }
                    else if(depth.find(station) == depth.end()) {
                        depth[station] = cur_depth + 1;
                        q.push(station);
                    }
                }
            }
        }
        return -1;
    }
};

过。感觉其他细节也不算很重要,不继续优化了。简单说下优化思路:首先是更多特判,比如超出范围。然后深度不需要全部记下来,因为BFS是按层往下的,记录层数就够了。

顺便感叹一下今天收到的春江花月夜键盘(帽)。当初看到的时候就觉得特别变态!鉴定为盲打速成键盘。不知为何虽然平时基本也是盲打,但是用这个就是容易打错。难道其实平时有用余光?神奇。

题目 Link:公交路线 815