//题目:
//返回从节点 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;
}