快递投放问题

题目描述

  • 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 0M,N100
  • 检查站禁止通行的包裹如果有多个,则以空格分开

输出描述

  • 输出不能送达的包裹,如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的意义是否相同?这里肯定是不相同的,为什么?
      • 因为一个道路可以连接两个站点
    • 那么根据输入描述,根据用例中的道路信息可以知道快递站点之间的关系如下。
    • image.png
    • 这里根据用例 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;
    }

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值