受限路径的长度(231周赛)

//题目:
//返回从节点 1 出发到节点 n 的 受限路径数 。
//路径的距离定义:这条路径上所有边的权重总和。
//用 distanceToLastNode(x) 表示节点 n 和 x 之间路径的最短距离。
//受限路径:满足 distanceToLastNode(zi) > distanceToLastNode(zi+1) 的一条路径,
//其中 0 <= i <= k-1 。
//题意分析;
//        问题1. 找到每个点的最短路径distance
//        问题2. 搜索点的路径中,distance降序排列的路径数量

//computeIfAbsent:如果指定的键尚未与值关联(或映射到 null),
// 则尝试使用给定的   映射函数   计算其值并将其输入此映射,除非 null ,返回计算得到的函数值。(减少分支语句)
public class Solution3_ans {
    public int countRestrictedPaths(int n, int[][] edges) {
        int cnt = 0;
        Map<Integer, List<int[]>> map = new HashMap<>();
        // 初始化邻接表
        for (int[] t : edges){
//            t[0]:起点,t[1]:终点,t[2]:长度
            int x = t[0];
            int y = t[1];
//            ArrayList k = new ArrayList();
//            map.putIfAbsent(x,k);
//            当不存在原来的映射时,k.add(new int[]{y,t[2]});否则用原来的映射的ArrayList添加
            //省去了分支结构
            map.computeIfAbsent(x, k -> new ArrayList<>()).add(new int[]{y, t[2]});
            map.computeIfAbsent(y, k -> new ArrayList<>()).add(new int[]{x, t[2]});
        }

        // 保存到n点的 最短距离 和 受限路径数
        int[] distance = findShortPath(map,n,n);
        Long[] mem = new Long[n + 1];

        cnt = (int)findLimitedPathCount(map,1,n,distance,mem);
        return cnt;
    }
//    定义 mem(i) 为从第i个点到结尾的受限路径数量,mem(1)就是我们的答案,
//    而 mem(n) = 1 是一个显而易见的起始条件。
//    记忆化搜索,递归去找满足条件的路径
    private long findLimitedPathCount(Map<Integer, List<int[]>> map, int start, int n, int[] distance, Long[] mem) {
        if(mem[start]!=null)
            return mem[start];      //这个点的首先路径已经计算过了,就可以直接返回路径数量
        if(start==n)
            return 1;               //如果到了最后一个点,只可能有一条路径,这条一定是受限路径,也就是递归的初始条件
        long sum = 0;               //路径总和
        List<int[]> list = map.getOrDefault(start,Collections.emptyList());
        //取得start节点的所有邻接点,递归检验这些相邻点可以产生多少受限路径
        for (int[] arr:list){
            int next = arr[0];
            //如果相邻节点距离比当前距离小,说明是受限路径
            if(distance[next] < distance[start]){//因为distance数组存放的就是单源路径最短距离
                sum += findLimitedPathCount(map,next,n,distance,mem);
                sum %= MOD;
            }
        }
        mem[start] = sum;//遍历完的结果就是当前节点的最短路径
        return sum;
    }

    //Dijstra
    //int[]的含义:第一个是这个点的下标,第个是链表头的点到这个点的距离
    //组合起来就是邻接表
    public int[] findShortPath( Map<Integer, List<int[]>> map, int n, int start) {
        // 初始化distance数组和visit数组,并用最大值填充作为不可达状态INF
        int[] distance = new int[n + 1];
        Arrays.fill(distance, Integer.MAX_VALUE);
        boolean[] visit = new boolean[n + 1];

        // 初始化,索引0和起点的distance为0
        distance[start] = 0;
        distance[0] = 0;//数组扩大了一个元素,所以0号元素就不要了,置为0可以防止被遍历到

        // 堆优化,将距离作为排序标准。
//         本体要求的是递减路径,所以按边的长度从大到小排列
//         意思 = 单独用传入距离是因为PriorityQueue的上浮规则决定
        PriorityQueue<int[]> queue = new PriorityQueue<>((o1, o2) -> o1[1] - o2[1]);
        // 把起点放进去,距离为0
        queue.offer(new int[]{start,0});

        while (!queue.isEmpty()) {
            // 当队列不空,拿出一个源出来
            Integer poll = queue.poll()[0];//当前取出的点可达的邻接点
            if(visit[poll]) continue;//访问过了就去下一个
            // 标记访问过了
            visit[poll] = true;

            List<int[]> list = map.getOrDefault(poll, Collections.emptyList());
            //如果这个点已经有邻接点,就取出这些点的序列,否则认为是空的,遍历自然没有意义
            //这样的好处,是不需要写分支结构
            // 遍历它的相邻节点
            for (int[] arr : list) {
                int next = arr[0];
                if (visit[next]) continue;
                // 更新到这个相邻节点的最短距离,与 poll出来的节点增加的距离 比较
                distance[next] = Math.min(distance[next], distance[poll] + arr[1]);
                //堆中新增节点,这里需要手动传入 next节点堆距离值。否则如果next在队列中,将永远无法上浮。
                queue.offer(new int[]{next,distance[next]});
                //已经找到最短路径的点,放入队列(visit标志位在前面已经改过了)
            }
        }
        return distance;
    }

    final int MOD = 1000000007;

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值