华为非AI方向0617笔试真题-公交线路换乘优化(多语言题解)

公交线路换乘优化(C++/Py/Java/Js/Go)题解

华为笔试真题 6月17号 非AI方向第一题 100分题型

题目内容

某城市有 NNN 条公交线路,每条线路用一个线性表示,包含该线路经过的所有站点(站点取值范围 1,20001,20001,2000),每条线路都是单向的,且站点顺序已按线路方向给出。

现在小明要从起点站A到终点站B,他可以选择,乘坐同一线路直达(如果A和B在同一条线路上)或最多换乘一次,换乘规则如下:

  • 只能在两条线路的交汇站换乘。
  • 起点站和终点站各算一次,换乘站只算一次。
  • 起点不会等于终点,终点一定在起点之后。
    请编写程序,计算从起点站点到终点站点需要经过的最少站点总数(包括起点和终点)。

输入描述

第一行:一个整数NNN,表示公交线路数量(1≤N≤201 \le N \le 201≤N≤20)

接下来NNN行,每行表示一条公交线路

每行第一个整数MMM表示该线路的站点数量(2≤M≤1002 \le M \le 1002≤M≤100)

后面MMM个整数表示公交线路经过的站点ID(按顺序)

最后一行:两个整数A,BA,BA,B,表示起点站点和终点站点

输出描述

一个整数,表示最少需要经过的站点数 如果无法到达,输出−1-1−1

样例1

输入

复制代码
2
4 1 2 3 4
3 5 6 7
1 7

输出

复制代码
-1

说明

线路111:111-222-333-444

线路222:555-666-777

没有交汇站,无法换乘,也无法直达

样例2

输入

复制代码
3
5 1 2 3 4 5
4 3 6 7 8
3 5 9 10
1 8

输出

复制代码
6

题解和思路

思路

实现思路:暴力枚举

  1. 题目只允许进行中转一次可以分析只会存在两种形式的方案
    • 只使用单条线路
    • 使用两条线路,线路之间存在中转点的情况,并且只能中转一次。
  2. 优先处理单条线路情况,线路必须存在终点和起点,并且起点位于终点之前。
  3. 枚举不同线路,其中线路A必须存在起点,线路B必须存在终点,枚举B终点之前的站点,如果在A起点之后存在该站点则可作为中转站,计算对应站点数量。
  4. 2、3中距离最小值就是结果。

算法时间复杂度为O(n^2 * m)

C++

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    int N;
    cin >> N;
    vector<vector<int>> roads(N);
    for (int i = 0; i < N; i++) {
        int m;
        cin >> m;
        for (int j = 0; j < m; j++) {
            int v;
            cin >> v;
            roads[i].push_back(v);
        }
    }
    
    int start,end;
    cin >> start >> end;
    
    vector<unordered_map<int, int>> pos(N);
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < roads[i].size(); j++) {
            pos[i][roads[i][j]] = j;
        }
    }
    
    
    int res = INT_MAX;
    // 考虑单条线路情况
    for (int i = 0; i < N; i++) {
        unordered_map<int, int> p = pos[i];
        if (p.count(start) && p.count(end) && p[start] < p[end]) {
            cout << "Xxx";
            res = min(res, p[end] - p[start] + 1);
        }
    }
    
//     考虑中转情况
    for (int i = 0; i < N; i++) {
        if (!pos[i].count(start)) {
            continue;
        }
        int pStart = pos[i][start];
        for (int j = 0; j < N; j++) {
            // 只考虑不同线路
            if (i == j) {
                continue;
            }
            // 不包含终点直接跳过
            if (!pos[j].count(end)) {
                continue;
            }
            int pEnd = pos[j][end];
            // 枚举换乘点,单项枚举j线路终点之前的站点
            for (int k = pEnd - 1; k >= 0; k--) {
                int transfer = roads[j][k];
                // i必须存在中转站并且在起点之后
                if (pos[i].count(transfer) && pos[i][transfer] > pStart) {
                    int cnt = (pEnd - k + 1) + (pos[i][transfer] - pStart + 1) - 1;
                    res = min(res, cnt);
                }
            }
        }
    }
    
    if (res == INT_MAX) {
        cout << -1;
        return 0;
    }
    cout << res;
    return 0;
} 

Java

java 复制代码
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);

        int N = sc.nextInt();
        List<List<Integer>> roads = new ArrayList<>();

        for (int i = 0; i < N; i++) {
            int m = sc.nextInt();
            List<Integer> line = new ArrayList<>();
            for (int j = 0; j < m; j++) {
                line.add(sc.nextInt());
            }
            roads.add(line);
        }

        int start = sc.nextInt();
        int end = sc.nextInt();

        List<Map<Integer, Integer>> pos = new ArrayList<>();
        for (int i = 0; i < N; i++) {
            Map<Integer, Integer> map = new HashMap<>();
            List<Integer> road = roads.get(i);
            for (int j = 0; j < road.size(); j++) {
                map.put(road.get(j), j);
            }
            pos.add(map);
        }

        int res = Integer.MAX_VALUE;

        // 考虑单条线路情况
        for (int i = 0; i < N; i++) {
            Map<Integer, Integer> p = pos.get(i);
            if (p.containsKey(start) && p.containsKey(end)
                    && p.get(start) < p.get(end)) {
                System.out.print("Xxx");
                res = Math.min(res, p.get(end) - p.get(start) + 1);
            }
        }

        // 考虑中转情况
        for (int i = 0; i < N; i++) {
            if (!pos.get(i).containsKey(start)) {
                continue;
            }

            int pStart = pos.get(i).get(start);

            for (int j = 0; j < N; j++) {
                // 只考虑不同线路
                if (i == j) {
                    continue;
                }

                // 不包含终点直接跳过
                if (!pos.get(j).containsKey(end)) {
                    continue;
                }

                int pEnd = pos.get(j).get(end);

                // 枚举换乘点,单项枚举j线路终点之前的站点
                for (int k = pEnd - 1; k >= 0; k--) {
                    int transfer = roads.get(j).get(k);

                    // i必须存在中转站并且在起点之后
                    if (pos.get(i).containsKey(transfer)
                            && pos.get(i).get(transfer) > pStart) {

                        int cnt =
                                (pEnd - k + 1)
                                        + (pos.get(i).get(transfer) - pStart + 1)
                                        - 1;

                        res = Math.min(res, cnt);
                    }
                }
            }
        }

        if (res == Integer.MAX_VALUE) {
            System.out.print(-1);
            return;
        }

        System.out.print(res);
    }
}

python

python 复制代码
import sys

data = list(map(int, sys.stdin.read().split()))
idx = 0

N = data[idx]
idx += 1

roads = []

for _ in range(N):
    m = data[idx]
    idx += 1

    road = data[idx:idx + m]
    idx += m

    roads.append(road)

start = data[idx]
idx += 1

end = data[idx]

pos = []

for road in roads:
    mp = {}
    for i, v in enumerate(road):
        mp[v] = i
    pos.append(mp)

res = float('inf')

# 考虑单条线路情况
for i in range(N):
    p = pos[i]

    if start in p and end in p and p[start] < p[end]:
        print("Xxx", end="")
        res = min(res, p[end] - p[start] + 1)

# 考虑中转情况
for i in range(N):
    if start not in pos[i]:
        continue

    p_start = pos[i][start]

    for j in range(N):
        # 只考虑不同线路
        if i == j:
            continue

        # 不包含终点直接跳过
        if end not in pos[j]:
            continue

        p_end = pos[j][end]

        # 枚举换乘点,单项枚举j线路终点之前的站点
        for k in range(p_end - 1, -1, -1):
            transfer = roads[j][k]

            # i必须存在中转站并且在起点之后
            if transfer in pos[i] and pos[i][transfer] > p_start:
                cnt = (
                    (p_end - k + 1)
                    + (pos[i][transfer] - p_start + 1)
                    - 1
                )

                res = min(res, cnt)

if res == float('inf'):
    print(-1)
else:
    print(res, end="")

Javascript

js 复制代码
const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

let input = [];

rl.on('line', line => {
    input.push(line);
});

rl.on('close', () => {
    const data = input.join(' ').trim().split(/\s+/).map(Number);

    let idx = 0;

    const N = data[idx++];

    const roads = [];

    for (let i = 0; i < N; i++) {
        const m = data[idx++];

        const road = [];
        for (let j = 0; j < m; j++) {
            road.push(data[idx++]);
        }

        roads.push(road);
    }

    const start = data[idx++];
    const end = data[idx++];

    const pos = [];

    for (let i = 0; i < N; i++) {
        const mp = new Map();

        for (let j = 0; j < roads[i].length; j++) {
            mp.set(roads[i][j], j);
        }

        pos.push(mp);
    }

    let res = Number.MAX_SAFE_INTEGER;

    // 考虑单条线路情况
    for (let i = 0; i < N; i++) {
        const p = pos[i];

        if (
            p.has(start) &&
            p.has(end) &&
            p.get(start) < p.get(end)
        ) {
            process.stdout.write("Xxx");
            res = Math.min(
                res,
                p.get(end) - p.get(start) + 1
            );
        }
    }

    // 考虑中转情况
    for (let i = 0; i < N; i++) {
        if (!pos[i].has(start)) {
            continue;
        }

        const pStart = pos[i].get(start);

        for (let j = 0; j < N; j++) {
            // 只考虑不同线路
            if (i === j) {
                continue;
            }

            // 不包含终点直接跳过
            if (!pos[j].has(end)) {
                continue;
            }

            const pEnd = pos[j].get(end);

            // 枚举换乘点,单项枚举j线路终点之前的站点
            for (let k = pEnd - 1; k >= 0; k--) {
                const transfer = roads[j][k];

                // i必须存在中转站并且在起点之后
                if (
                    pos[i].has(transfer) &&
                    pos[i].get(transfer) > pStart
                ) {
                    const cnt =
                        (pEnd - k + 1) +
                        (pos[i].get(transfer) - pStart + 1) -
                        1;

                    res = Math.min(res, cnt);
                }
            }
        }
    }

    if (res === Number.MAX_SAFE_INTEGER) {
        console.log(-1);
        return;
    }

    console.log(res);
});

Go

go 复制代码
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	in := bufio.NewReader(os.Stdin)

	var N int
	fmt.Fscan(in, &N)

	roads := make([][]int, N)

	for i := 0; i < N; i++ {
		var m int
		fmt.Fscan(in, &m)

		roads[i] = make([]int, m)

		for j := 0; j < m; j++ {
			fmt.Fscan(in, &roads[i][j])
		}
	}

	var start, end int
	fmt.Fscan(in, &start, &end)

	pos := make([]map[int]int, N)

	for i := 0; i < N; i++ {
		pos[i] = make(map[int]int)

		for j, v := range roads[i] {
			pos[i][v] = j
		}
	}

	res := int(^uint(0) >> 1)

	// 考虑单条线路情况
	for i := 0; i < N; i++ {
		p := pos[i]

		startPos, ok1 := p[start]
		endPos, ok2 := p[end]

		if ok1 && ok2 && startPos < endPos {
			fmt.Print("Xxx")
			if endPos-startPos+1 < res {
				res = endPos - startPos + 1
			}
		}
	}

	// 考虑中转情况
	for i := 0; i < N; i++ {
		pStart, ok := pos[i][start]
		if !ok {
			continue
		}

		for j := 0; j < N; j++ {
			// 只考虑不同线路
			if i == j {
				continue
			}

			// 不包含终点直接跳过
			pEnd, ok := pos[j][end]
			if !ok {
				continue
			}

			// 枚举换乘点,单项枚举j线路终点之前的站点
			for k := pEnd - 1; k >= 0; k-- {
				transfer := roads[j][k]

				// i必须存在中转站并且在起点之后
				if transferPos, ok := pos[i][transfer]; ok && transferPos > pStart {

					cnt :=
						(pEnd-k+1) +
							(transferPos-pStart+1) -
							1

					if cnt < res {
						res = cnt
					}
				}
			}
		}
	}

	if res == int(^uint(0)>>1) {
		fmt.Print(-1)
		return
	}

	fmt.Print(res)
}