【华为机试】815. 公交路线
文章目录
- 815. 公交路线
- 题目描述
- 示例 1:
- 示例 2:
- 提示:
- 解题思路
- 算法分析
- 核心思想
- 算法对比
- 算法流程图
- BFS路线图构建流程
- 双向BFS优化流程
- 图建模策略决策树
- 复杂度分析
- 时间复杂度
- 空间复杂度
- 实现技巧
- 1. 车站到路线映射优化
- 2. 路线连通性检查
- 3. BFS搜索优化
- 算法优化策略
- 1. 预处理优化
- 2. 搜索优化
- 3. 数据结构优化
- 边界情况处理
- 1. 特殊情况
- 2. 数据验证
- 应用场景
- 测试用例设计
- 基础测试
- 边界测试
- 性能测试
- 实战技巧总结
- 完整题解代码
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
提示:
- 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搜索:寻找从起点所在路线到终点所在路线的最短路径
- 换乘策略:通过共同车站实现不同路线之间的换乘
- 最优解:BFS保证找到最少换乘次数
算法对比
算法 | 时间复杂度 | 空间复杂度 | 特点 |
---|---|---|---|
BFS路线图 | O(N²+S) | O(N²+S) | 以路线为节点,经典解法 |
BFS车站图 | O(S²) | O(S²) | 以车站为节点,直观理解 |
双向BFS | O(N²+S) | O(N²+S) | 从两端搜索,提升效率 |
A*搜索 | O(N²+S) | O(N²+S) | 启发式搜索,理论最优 |
注:N为路线数量,S为车站总数
算法流程图
graph TDA[开始: routes, source, target] --> B{source == target?}B -->|是| C[返回 0]B -->|否| D[构建车站到路线的映射]D --> E[找到包含source的所有路线]E --> F[找到包含target的所有路线]F --> G{起点或终点路线为空?}G -->|是| H[返回 -1]G -->|否| I[BFS搜索最短路径]I --> J[初始化队列:起点路线集合]J --> K{队列非空?}K -->|否| L[返回 -1 无法到达]K -->|是| M[取出当前路线]M --> N{当前路线包含target?}N -->|是| O[返回当前步数]N -->|否| P[遍历当前路线的所有车站]P --> Q[找到每个车站的所有其他路线]Q --> R[将未访问的路线加入队列]R --> S[步数+1]S --> K
BFS路线图构建流程
双向BFS优化流程
graph TDA[双向BFS开始] --> B[初始化起点队列和终点队列]B --> C[起点队列:包含source的路线]C --> D[终点队列:包含target的路线]D --> E{两个队列都非空?}E -->|否| F[返回 -1]E -->|是| G[选择较小的队列扩展]G --> H[扩展当前队列]H --> I{访问的路线在另一队列中?}I -->|是| J[找到连接点,返回步数]I -->|否| K[继续扩展]K --> L[更新访问状态]L --> E
图建模策略决策树
graph TDA[图建模策略] --> B{以什么为节点?}B -->|车站| C[车站图模型]B -->|路线| D[路线图模型]C --> E[车站间直接连接]E --> F[空间复杂度: O(S²)]F --> G[适合车站数少的情况]D --> H[路线间通过共同车站连接]H --> I[空间复杂度: O(N²)]I --> J[适合路线数少的情况]G --> K[选择最优策略]J --> KK --> L[根据数据规模决定]
复杂度分析
时间复杂度
- BFS路线图:O(N² + S),其中N为路线数,S为车站总数
- BFS车站图:O(S²),需要构建车站间的连接
- 双向BFS:O(N² + S),理论上快一倍
- 预处理时间:O(S),构建车站到路线的映射
空间复杂度
- 路线图模型:O(N² + S),存储路线连接和车站映射
- 车站图模型:O(S²),存储车站间连接
- BFS队列:O(N),最多存储所有路线
- 访问标记:O(N),记录已访问路线
实现技巧
1. 车站到路线映射优化
// 构建车站到路线的映射
stationToRoutes := make(map[int][]int)
for i, route := range routes {for _, station := range route {stationToRoutes[station] = append(stationToRoutes[station], i)}
}
2. 路线连通性检查
// 检查两条路线是否有共同车站
func hasCommonStation(route1, route2 []int) bool {set := make(map[int]bool)for _, station := range route1 {set[station] = true}for _, station := range route2 {if set[station] {return true}}return false
}
3. BFS搜索优化
// 使用队列进行BFS
type QueueItem struct {routeIndex intsteps int
}queue := []QueueItem{}
visited := make([]bool, len(routes))
算法优化策略
1. 预处理优化
- 预先构建车站到路线的映射
- 预计算路线间的连通关系
- 去重处理减少重复计算
2. 搜索优化
- 双向BFS减少搜索空间
- 优先队列优化搜索顺序
- 剪枝策略避免无效搜索
3. 数据结构优化
- 使用集合快速判断车站存在性
- 哈希表优化路线查找
- 位图优化访问状态存储
边界情况处理
1. 特殊情况
- source == target:直接返回0
- 起点或终点不在任何路线:返回-1
- 空路线数组:返回-1
2. 数据验证
- 检查路线数据的有效性
- 验证车站编号范围
- 处理重复车站情况
应用场景
- 交通规划:城市公交换乘优化
- 网络路由:最短路径查找
- 社交网络:用户关系传播
- 游戏开发:地图导航系统
- 物流优化:配送路线规划
测试用例设计
基础测试
- 直达路线:无需换乘
- 一次换乘:通过中转站
- 多次换乘:复杂路线网络
边界测试
- 起终点相同
- 无法到达的目标
- 单条路线情况
- 大规模路线网络
性能测试
- 最大路线数(500)
- 最大车站数(10^5)
- 复杂连通图
- 稀疏连通图
实战技巧总结
- 图建模:合理选择节点类型(车站vs路线)
- BFS搜索:保证最短路径的正确性
- 预处理:空间换时间,提升查询效率
- 优化策略:根据数据特点选择最优算法
- 边界处理:完善的异常情况处理
- 代码结构:清晰的模块化设计
完整题解代码
package mainimport ("fmt""strings""time"
)// 解法一:BFS路线图(推荐解法)
// 时间复杂度:O(N²+S),空间复杂度:O(N²+S)
func numBusesToDestination(routes [][]int, source int, target int) int {if source == target {return 0}if len(routes) == 0 {return -1}// 构建车站到路线的映射stationToRoutes := make(map[int][]int)for i, route := range routes {for _, station := range route {stationToRoutes[station] = append(stationToRoutes[station], i)}}// 检查起点和终点是否存在于路线中sourceRoutes, sourceExists := stationToRoutes[source]targetRoutes, targetExists := stationToRoutes[target]if !sourceExists || !targetExists {return -1}// 检查是否可以直达sourceSet := make(map[int]bool)for _, route := range sourceRoutes {sourceSet[route] = true}for _, route := range targetRoutes {if sourceSet[route] {return 1 // 同一条路线,只需一辆公交车}}// BFS搜索queue := make([]int, 0)visited := make([]bool, len(routes))// 将包含起点的所有路线加入队列for _, routeIdx := range sourceRoutes {queue = append(queue, routeIdx)visited[routeIdx] = true}steps := 1for len(queue) > 0 {size := len(queue)// 处理当前层的所有路线for i := 0; i < size; i++ {currentRoute := queue[i]// 遍历当前路线的所有车站for _, station := range routes[currentRoute] {// 获取经过该车站的所有路线for _, nextRoute := range stationToRoutes[station] {if visited[nextRoute] {continue}// 检查是否到达目标if contains(routes[nextRoute], target) {return steps + 1}// 标记并加入队列visited[nextRoute] = truequeue = append(queue, nextRoute)}}}queue = queue[size:] // 移除已处理的元素steps++}return -1
}// 解法二:BFS车站图
// 时间复杂度:O(S²),空间复杂度:O(S²)
func numBusesToDestinationStations(routes [][]int, source int, target int) int {if source == target {return 0}// 构建车站连接图stationGraph := make(map[int]map[int]bool)// 同一路线的车站相互连通for _, route := range routes {for i := 0; i < len(route); i++ {if stationGraph[route[i]] == nil {stationGraph[route[i]] = make(map[int]bool)}for j := 0; j < len(route); j++ {if i != j {stationGraph[route[i]][route[j]] = true}}}}// 检查起点是否存在if stationGraph[source] == nil {return -1}// BFS搜索最短路径queue := []int{source}visited := make(map[int]bool)visited[source] = truesteps := 0for len(queue) > 0 {size := len(queue)steps++for i := 0; i < size; i++ {currentStation := queue[i]// 遍历所有相邻车站for nextStation := range stationGraph[currentStation] {if nextStation == target {return steps}if !visited[nextStation] {visited[nextStation] = truequeue = append(queue, nextStation)}}}queue = queue[size:]}return -1
}// 解法三:双向BFS(优化版本)
// 时间复杂度:O(N²+S),空间复杂度:O(N²+S)
func numBusesToDestinationBidirectional(routes [][]int, source int, target int) int {if source == target {return 0}// 构建车站到路线的映射stationToRoutes := make(map[int][]int)for i, route := range routes {for _, station := range route {stationToRoutes[station] = append(stationToRoutes[station], i)}}sourceRoutes, sourceExists := stationToRoutes[source]targetRoutes, targetExists := stationToRoutes[target]if !sourceExists || !targetExists {return -1}// 初始化双向搜索forwardQueue := make(map[int]bool)backwardQueue := make(map[int]bool)forwardVisited := make(map[int]int)backwardVisited := make(map[int]int)// 起点路线for _, route := range sourceRoutes {forwardQueue[route] = trueforwardVisited[route] = 1}// 终点路线for _, route := range targetRoutes {backwardQueue[route] = truebackwardVisited[route] = 1// 检查是否可以直达if forwardVisited[route] > 0 {return 1}}// 双向BFSfor len(forwardQueue) > 0 && len(backwardQueue) > 0 {// 选择较小的队列进行扩展if len(forwardQueue) > len(backwardQueue) {if result := expandQueue(backwardQueue, backwardVisited, forwardVisited, routes, stationToRoutes); result != -1 {return result}} else {if result := expandQueue(forwardQueue, forwardVisited, backwardVisited, routes, stationToRoutes); result != -1 {return result}}}return -1
}// 扩展队列的辅助函数
func expandQueue(queue map[int]bool, visited map[int]int, otherVisited map[int]int,routes [][]int, stationToRoutes map[int][]int) int {nextQueue := make(map[int]bool)for routeIdx := range queue {currentSteps := visited[routeIdx]for _, station := range routes[routeIdx] {for _, nextRoute := range stationToRoutes[station] {if visited[nextRoute] > 0 {continue}if otherVisited[nextRoute] > 0 {return currentSteps + otherVisited[nextRoute]}nextQueue[nextRoute] = truevisited[nextRoute] = currentSteps + 1}}}// 清空当前队列,用新队列替代for k := range queue {delete(queue, k)}for k := range nextQueue {queue[k] = true}return -1
}// 解法四:A*搜索(启发式优化)
// 时间复杂度:O(N²+S),空间复杂度:O(N²+S)
func numBusesToDestinationAStar(routes [][]int, source int, target int) int {if source == target {return 0}// 构建车站到路线的映射stationToRoutes := make(map[int][]int)for i, route := range routes {for _, station := range route {stationToRoutes[station] = append(stationToRoutes[station], i)}}sourceRoutes, sourceExists := stationToRoutes[source]targetRoutes, targetExists := stationToRoutes[target]if !sourceExists || !targetExists {return -1}// 构建目标路线集合targetSet := make(map[int]bool)for _, route := range targetRoutes {targetSet[route] = true}// A*搜索使用优先队列type Node struct {routeIdx intsteps intpriority int // f(n) = g(n) + h(n)}// 简单优先队列实现pq := []Node{}visited := make(map[int]bool)// 启发式函数:如果路线包含目标站点,启发值为0,否则为1heuristic := func(routeIdx int) int {if targetSet[routeIdx] {return 0}return 1}// 初始化起点路线for _, routeIdx := range sourceRoutes {if targetSet[routeIdx] {return 1}h := heuristic(routeIdx)pq = append(pq, Node{routeIdx, 1, 1 + h})}for len(pq) > 0 {// 简单的优先队列取最小值minIdx := 0for i := 1; i < len(pq); i++ {if pq[i].priority < pq[minIdx].priority {minIdx = i}}current := pq[minIdx]pq = append(pq[:minIdx], pq[minIdx+1:]...)if visited[current.routeIdx] {continue}visited[current.routeIdx] = true// 扩展当前路线for _, station := range routes[current.routeIdx] {for _, nextRoute := range stationToRoutes[station] {if visited[nextRoute] {continue}if targetSet[nextRoute] {return current.steps + 1}h := heuristic(nextRoute)newNode := Node{nextRoute, current.steps + 1, current.steps + 1 + h}pq = append(pq, newNode)}}}return -1
}// 辅助函数:检查数组是否包含元素
func contains(arr []int, target int) bool {for _, val := range arr {if val == target {return true}}return false
}// 辅助函数:构建路线连通图
func buildRouteGraph(routes [][]int) [][]bool {n := len(routes)graph := make([][]bool, n)for i := range graph {graph[i] = make([]bool, n)}// 检查路线间是否有共同车站for i := 0; i < n; i++ {for j := i + 1; j < n; j++ {if hasCommonStation(routes[i], routes[j]) {graph[i][j] = truegraph[j][i] = true}}}return graph
}// 检查两条路线是否有共同车站
func hasCommonStation(route1, route2 []int) bool {set := make(map[int]bool)for _, station := range route1 {set[station] = true}for _, station := range route2 {if set[station] {return true}}return false
}// 公交系统模拟器
type BusSystem struct {routes [][]intstationToRoutes map[int][]introuteGraph [][]booltotalStations inttotalRoutes int
}// 创建公交系统
func newBusSystem(routes [][]int) *BusSystem {bs := &BusSystem{routes: routes,stationToRoutes: make(map[int][]int),totalRoutes: len(routes),}// 构建车站到路线映射stationSet := make(map[int]bool)for i, route := range routes {for _, station := range route {bs.stationToRoutes[station] = append(bs.stationToRoutes[station], i)stationSet[station] = true}}bs.totalStations = len(stationSet)// 构建路线连通图bs.routeGraph = buildRouteGraph(routes)return bs
}// 查找最短路径
func (bs *BusSystem) findShortestPath(source, target int) int {return numBusesToDestination(bs.routes, source, target)
}// 获取系统统计信息
func (bs *BusSystem) getStats() map[string]interface{} {return map[string]interface{}{"total_routes": bs.totalRoutes,"total_stations": bs.totalStations,"avg_route_len": bs.getAverageRouteLength(),"connectivity": bs.getConnectivity(),}
}// 计算平均路线长度
func (bs *BusSystem) getAverageRouteLength() float64 {total := 0for _, route := range bs.routes {total += len(route)}return float64(total) / float64(bs.totalRoutes)
}// 计算连通性
func (bs *BusSystem) getConnectivity() float64 {connections := 0for i := 0; i < bs.totalRoutes; i++ {for j := i + 1; j < bs.totalRoutes; j++ {if bs.routeGraph[i][j] {connections++}}}totalPairs := bs.totalRoutes * (bs.totalRoutes - 1) / 2if totalPairs == 0 {return 0}return float64(connections) / float64(totalPairs)
}// 测试函数
func testBusRoutes() {testCases := []struct {routes [][]intsource inttarget intexpected intdesc string}{{[][]int{{1, 2, 7}, {3, 6, 7}},1, 6, 2,"示例1:需要换乘一次",},{[][]int{{7, 12}, {4, 5, 15}, {6}, {15, 19}, {9, 12, 13}},15, 12, -1,"示例2:无法到达",},{[][]int{{1, 2, 3}, {4, 5, 6}},1, 6, -1,"两条独立路线:无法到达",},{[][]int{{1, 2, 3, 4, 5}},1, 5, 1,"单条路线:直达",},{[][]int{{1, 2}, {2, 3}, {3, 4}},1, 4, 3,"链式连接:需要多次换乘",},{[][]int{{1, 2, 3}, {2, 4, 5}, {3, 5, 6}},1, 6, 2,"网状结构:多条路径",},{[][]int{{1}, {1}},1, 1, 0,"起终点相同",},{[][]int{{1, 2, 3}, {4, 5, 6}, {1, 6}},2, 5, 3,"桥接路线:需要三次换乘",},{[][]int{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}},0, 9, 1,"长路线测试",},{[][]int{{1, 2}, {1, 3}, {1, 4}},2, 4, 2,"星型结构:中心换乘",},}fmt.Println("=== 公交路线测试 ===")fmt.Println()for i, tc := range testCases {// 测试不同算法result1 := numBusesToDestination(tc.routes, tc.source, tc.target)result2 := numBusesToDestinationBidirectional(tc.routes, tc.source, tc.target)result3 := numBusesToDestinationAStar(tc.routes, tc.source, tc.target)status := "✅"if result1 != tc.expected {status = "❌"}fmt.Printf("测试 %d: %s\n", i+1, tc.desc)fmt.Printf("路线: %v\n", tc.routes)fmt.Printf("起点: %d, 终点: %d\n", tc.source, tc.target)fmt.Printf("期望: %d, 实际: %d\n", tc.expected, result1)// 验证算法一致性consistent := result1 == result2 && result2 == result3fmt.Printf("算法一致性: %t (BFS:%d, 双向:%d, A*:%d)\n",consistent, result1, result2, result3)fmt.Printf("结果: %s\n", status)fmt.Println(strings.Repeat("-", 50))}
}// 性能测试
func benchmarkBusRoutes() {fmt.Println()fmt.Println("=== 性能测试 ===")fmt.Println()// 构造测试数据testData := []struct {routes [][]intsource inttarget intdesc string}{{generateRoutes(10, 5, 10),1, 45,"小规模:10条路线",},{generateRoutes(50, 10, 100),5, 450,"中等规模:50条路线",},{generateRoutes(100, 20, 200),10, 950,"大规模:100条路线",},{generateLinearRoutes(200),1, 399,"最坏情况:线性路线",},}algorithms := []struct {name stringfn func([][]int, int, int) int}{{"BFS路线图", numBusesToDestination},{"双向BFS", numBusesToDestinationBidirectional},{"A*搜索", numBusesToDestinationAStar},}for _, data := range testData {fmt.Printf("%s:\n", data.desc)// 创建公交系统分析bs := newBusSystem(data.routes)stats := bs.getStats()fmt.Printf(" 路线数: %d, 车站数: %d, 平均长度: %.1f, 连通性: %.2f\n",stats["total_routes"], stats["total_stations"],stats["avg_route_len"], stats["connectivity"])for _, algo := range algorithms {start := time.Now()result := algo.fn(data.routes, data.source, data.target)duration := time.Since(start)fmt.Printf(" %s: 结果=%d, 耗时=%v\n", algo.name, result, duration)}fmt.Println()}
}// 生成测试路线
func generateRoutes(numRoutes, avgLength, maxStation int) [][]int {routes := make([][]int, numRoutes)for i := 0; i < numRoutes; i++ {length := avgLength + (i%5 - 2) // 长度在avgLength±2之间变化if length < 2 {length = 2}route := make([]int, length)start := (i * maxStation / numRoutes) + 1for j := 0; j < length; j++ {route[j] = start + j*2 // 确保站点分布}routes[i] = route}return routes
}// 生成线性路线(最坏情况)
func generateLinearRoutes(numRoutes int) [][]int {routes := make([][]int, numRoutes)for i := 0; i < numRoutes; i++ {// 每条路线只连接相邻的两个站点routes[i] = []int{i*2 + 1, i*2 + 2}}return routes
}// 路径可视化
func visualizePath(routes [][]int, source, target int) {fmt.Println()fmt.Println("=== 路径可视化 ===")bs := newBusSystem(routes)result := bs.findShortestPath(source, target)fmt.Printf("从车站 %d 到车站 %d:\n", source, target)fmt.Printf("最少换乘次数: %d\n", result)if result == -1 {fmt.Println("无法到达目标车站")return}fmt.Println("\n路线信息:")for i, route := range routes {fmt.Printf("路线 %d: %v\n", i, route)hasSource := contains(route, source)hasTarget := contains(route, target)if hasSource && hasTarget {fmt.Printf(" * 直达路线!\n")} else if hasSource {fmt.Printf(" * 包含起点\n")} else if hasTarget {fmt.Printf(" * 包含终点\n")}}// 显示连通信息fmt.Println("\n路线连通性:")for i := 0; i < len(routes); i++ {connections := []int{}for j := 0; j < len(routes); j++ {if i != j && hasCommonStation(routes[i], routes[j]) {connections = append(connections, j)}}if len(connections) > 0 {fmt.Printf("路线 %d 连接到: %v\n", i, connections)}}
}// 算法比较演示
func demonstrateAlgorithms() {fmt.Println()fmt.Println("=== 算法实现对比 ===")routes := [][]int{{1, 2, 7},{3, 6, 7},{2, 4, 6},{4, 8, 9},}source, target := 1, 9fmt.Printf("测试路线: %v\n", routes)fmt.Printf("起点: %d, 终点: %d\n", source, target)algorithms := []struct {name stringfn func([][]int, int, int) intdesc string}{{"BFS路线图", numBusesToDestination, "以路线为节点的图搜索"},{"双向BFS", numBusesToDestinationBidirectional, "从两端同时搜索"},{"A*搜索", numBusesToDestinationAStar, "启发式搜索优化"},}for _, algo := range algorithms {start := time.Now()result := algo.fn(routes, source, target)duration := time.Since(start)fmt.Printf("\n%s (%s):\n", algo.name, algo.desc)fmt.Printf(" 结果: %d\n", result)fmt.Printf(" 耗时: %v\n", duration)}
}func main() {fmt.Println("815. 公交路线 - 多种解法实现")fmt.Println("==============================")// 基础功能测试testBusRoutes()// 性能对比测试benchmarkBusRoutes()// 路径可视化routes := [][]int{{1, 2, 7}, {3, 6, 7}}visualizePath(routes, 1, 6)// 算法对比演示demonstrateAlgorithms()// 展示算法特点fmt.Println()fmt.Println("=== 算法特点分析 ===")fmt.Println("1. BFS路线图:以路线为节点,保证最短路径")fmt.Println("2. 双向BFS:从两端搜索,理论上快一倍")fmt.Println("3. A*搜索:启发式优化,适合特定场景")fmt.Println("4. 车站图:直观但空间复杂度较高")fmt.Println()fmt.Println("=== 公交路线问题技巧 ===")fmt.Println("• 图建模:合理选择节点类型(路线vs车站)")fmt.Println("• BFS搜索:保证找到最少换乘次数")fmt.Println("• 预处理:构建高效的数据结构")fmt.Println("• 优化策略:双向搜索和启发式方法")fmt.Println("• 边界处理:起终点相同、无法到达等情况")fmt.Println("• 系统设计:可扩展的公交系统架构")
}