leetcode
leetcode 2851 ~ 2900
快速公交

快速公交

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 56 ms, 内存: 17.8 MB


/*
 * Problem: Xiao Kou plans to go to the Autumn City Market. Due to the crowd, his walking speed is affected:
 * - Moving from station x to station x + 1 costs 'inc' time.
 * - Moving from station x to station x - 1 costs 'dec' time.
 * There are 'm' buses, numbered from 0 to m-1. Xiao Kou can take bus i to move from station x to station jump[i]*x at the cost of cost[i].
 * Xiao Kou can take any bus any number of times. The start station is 0 and the target station is 'target'.
 * Return the minimum time Xiao Kou needs to reach the target station, modulo 1000000007 (1e9 + 7).
 * Note: Xiao Kou can reach a station number greater than 'target' during the process.
 */

import java.util.*;
import java.util.stream.Stream;

public class MinTimeToTargetStream {
    public int minCost(int target, int inc, int dec, int[] jump, int[] cost) {
        long mod = 1000000007;
        Map<Long, Long> dp = new HashMap<>();
        dp.put(0L, 0L);
        PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(a -> a[1]));
        pq.add(new long[]{0, 0});

        while (!pq.isEmpty()) {
            long[] curr = pq.poll();
            long pos = curr[0], time = curr[1];
            if (pos == target) return (int)(time % mod);

            if (dp.getOrDefault(pos, Long.MAX_VALUE) < time) continue;

            Stream.of(new long[]{pos + 1, time + inc},
                      pos > 0 ? new long[]{pos - 1, time + dec} : null)
                  .filter(Objects::nonNull)
                  .forEach(next -> {
                      if (!dp.containsKey(next[0]) || dp.get(next[0]) > next[1]) {
                          dp.put(next[0], next[1]);
                          pq.add(next);
                      }
                  });

            for (int i = 0; i < jump.length; i++) {
                long newPos = pos * jump[i];
                if (!dp.containsKey(newPos) || dp.get(newPos) > time + cost[i]) {
                    dp.put(newPos, time + cost[i]);
                    pq.add(new long[]{newPos, time + cost[i]});
                }
            }
        }
        return -1;
    }
}

解释

方法:

本题解采用了优先队列(最小堆)结合广度优先搜索的方法来寻找到达目标站点的最小花费。初始时,将目标站点放入优先队列,代表从目标站点逆向回到起点的代价为零(逆向思维)。然后逐渐将所有可能的站点及其花费压入优先队列。对于每个站点,计算直接步行到起点的花费,以及通过公交车跳跃后的新站点和对应花费。每次从优先队列中取出当前花费最小的站点,更新答案,并检查是否已到起点。如果达到起点站,记录总花费并结束搜索。否则,继续探索下一跳的可能站点。利用visited_stations集合避免重复处理同一站点,以优化搜索效率。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
题解中提到使用逆向思维,即从目标站点逆向回到起点,这种方法是如何确保找到最小花费的路径的?
逆向搜索的核心思想是将目标视为起点,从而逆向考虑到达实际起点的路径和费用。这种方法通过每次优先处理费用最小的站点(使用优先队列),确保了首先考虑的路径总是当前可能的最小费用路径。逆向处理允许算法在找到到达起点的可行路径时,已经确保这是花费最小的路径,因为所有后续考虑的路径都将具有更高的起始费用。这种策略有效地减少了搜索空间,并优化了性能。
🦆
为什么在处理过程中当`total_cost`大于已知的最小值`ans`时,可以安全地终止处理当前站点?
在算法执行过程中,如果某个站点的处理费用`total_cost`已经超过了之前找到的最小费用`ans`,那么继续处理这个站点只会导致更高的总费用。因为从这个站点出发的任何进一步路径都会基于这个已经较高的`total_cost`,无法产生比`ans`更低的结果。基于最小堆的性质,队列中剩余的所有站点的开始处理费用也都不会低于当前的`total_cost`。因此,可以安全地忽略或停止处理费用已超标的站点,从而提高算法的效率。
🦆
题解中使用了优先队列(最小堆),这种数据结构在此问题中的主要优势是什么?
优先队列(最小堆)在这类问题中的主要优势在于它能够保证每次从队列中取出的都是当前未处理站点中费用最小的一个。这种特性使得算法在每一步都优先考虑当前可能的最低成本路径,从而有效地找到全局最优解。此外,使用优先队列还可以高效地管理和更新待处理的站点集合,尤其是在涉及大量数据和频繁更新时,最小堆能够在对数时间内完成插入和删除最小元素操作,极大地优化了性能。

相关问题