题目描述
- 有 N N N个快递站点用字符串标识,某些站点之间有道路连接。
- 每个站点有一些包裹要运输,每个站点间的包裹不重复,路上有检查站会导致部分货物无法通行,计算哪些货物无法正常投递?
输入描述
- 第一行输入 M N M\quad N MN, M M M个包裹和 N N N个道路信息
- 0 ≤ M , N ≤ 100 0\leq M,N \leq 100 0≤M,N≤100
- 检查站禁止通行的包裹如果有多个,则以空格分开
输出描述
- 输出不能送达的包裹,如
package2、package4
- 如果所有的包裹都能送达则输出:
none
- 输出结果按照升序排列
用例
4 2
package1 A C
package2 A C
package3 B C
package4 A C
A B package1
A C package2
--输出
package2
题目解析
这个题目极其难以理解,文字越少越难理解。
提取关键点
- 首先注意到数据范围是 [ 0 , 1 0 2 ] [0, 10^2] [0,102]
- 这个数据范围下,可以重复多次遍历
- M M M个包裹信息,这快比较好理解,如用例
package1 A C
:表示了需要把包裹package1
从站点A
运送到 站点C
- 这里隐藏一个信息,是否存在这样的用例
package1 C A
,即把包裹从站点C
运送到 站点A
- 这一点还是要考虑到的
- 根据题目描述和输入描述
- 题目描述:有 N N N 个快递站点用字符串标识,某些站点之间有道路连接
- 输入描述: N N N个道路信息
- 这两个 N N N的意义是否相同?这里肯定是不相同的,为什么?
- 因为一个道路可以连接两个站点
- 那么根据输入描述,根据用例中的道路信息可以知道快递站点之间的关系如下。
- 这里根据用例
package3 B C
可以得到一个关键信息,快递投放可以通过站点之间进行中转 - 至此,分析完毕,问题转化成了求图中是否存在有效路径的问题.
show code
package com.hw;
import java.util.*;
/**
* desc :
* <p>
* create time : 2023/8/17 17:23
*/
public class DeliverPathFinderBetter {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String[] split = in.nextLine().split(" ");
// M 个包裹.
int M = Integer.parseInt(split[0]);
// N 个道路信息.
int N = Integer.parseInt(split[1]);
// 包裹信息,表示包裹需要从 一个站点运输到另一个站点.
String[][] pkgMsgS = new String[M][];
for (int i = 0; i < M; i++) {
pkgMsgS[i] = in.nextLine().split(" ");
}
// 道路信息以及在目标道路上禁止运送的包裹信息.
String[][] banPkgS = new String[N][];
for (int i = 0; i < N; i++) {
banPkgS[i] = in.nextLine().split(" ");
}
deliverPathFinderBetter(pkgMsgS, banPkgS);
}
private static void deliverPathFinderBetter(String[][] pkgMsgS, String[][] banPkgS) {
// 包裹运送信息--适合用 map 来存储
Map<String, String[]> deliverMap = new HashMap<>();
for (String[] pkgMsg : pkgMsgS) {
String[] startToEnd = {
pkgMsg[1], pkgMsg[2]};
// 包裹不能重复,直接 put.
deliverMap.put(pkgMsg[0], startToEnd);
}
// 根据道路信息,构造图数据.
Map<String, List<String>> graph = new HashMap<>();
// 用一个map,保存 禁止运输的 包裹信息.
Map<String, Set<String>> banMap = new HashMap<>();
// 处理数据.
for (String[] banPkg : banPkgS) {
String site1 = banPkg[0];
String site2 = banPkg[1];
String[] bans = Arrays.copyOfRange(banPkg, 2, banPkg.length);
// 建图数据结构.
graph.computeIfAbsent(site1, k -> new ArrayList<>()).add(site2);
graph.computeIfAbsent(site2, k -> new ArrayList<>()).add(site1);
String key = (site1.charAt(0) - 'A') < (site2.charAt(0) - 'A') ? (site1 + "-" + site2) : (site2 + "-" + site1);
// 禁止通行的包裹列表
banMap.computeIfAbsent(key, k -> new HashSet<>()).addAll(Arrays.asList(bans));
}
dealPkgS(deliverMap, graph, banMap);
}
private static void dealPkgS(Map<String, String[]> deliverMap, Map<String, List<String>> graph, Map<String, Set<String>> banMap) {
Set<String> unreachablePackages = new TreeSet<>();
for (String pkg : deliverMap.keySet()) {
// 当前将要运送的包裹是 pkg, 需要运送其从站点 start 到 end .
String[] path = deliverMap.get(pkg);
String start = path[0].charAt(0) - 'A' < path[1].charAt(0) - 'A' ? path[0] : path[1];
String end = path[0].charAt(0) - 'A' < path[1].charAt(0) - 'A' ? path[1] : path[0];
// 查看是否存在可通行的路径.
// 用一个set记录路径——即所到达的站点.
Set<String> visited = new HashSet<>();
if(!dfs(pkg, start, end, graph, visited, banMap)) {
unreachablePackages.add(pkg);
}
}
if(unreachablePackages.size() == 0) {
System.out.println("none");
} else {
for (String unreachablePackage : unreachablePackages) {
System.out.print(unreachablePackage + " ");
}
}
}
private static boolean dfs(String pkg,
String start,
String end,
Map<String, List<String>> graph,
Set<String> visited,
Map<String, Set<String>> banMap) {
if (start.equals(end)) {
// 到达了目标站点,那么这个时候返回 true ,表示该包裹可以到达目标站点.
return true;
}
visited.add(start);
for (String nextSite : graph.getOrDefault(start, Collections.emptyList())) {
if(visited.contains(nextSite)) {
// 去过的站点跳过.
continue;
}
String key = start.charAt(0) - 'A' < nextSite.charAt(0) - 'A' ? start + "-" + nextSite : nextSite + "-" + start;
if(banMap.containsKey(key) && banMap.get(key).contains(pkg)) {
// 当前路径不允许当前包裹通过.
continue;
}
if(dfs(pkg, nextSite, end, graph, visited, banMap)) {
return true;
}
}
return false;
}
}