50青海湖租车题解 | 豆包MarsCode AI刷题

56 阅读4分钟

最优加油方案问题题解

问题描述

小F计划从青海湖出发,前往一个遥远的景点X进行旅游。景点X可能是“敦煌”或“月牙泉”,线路的路径是唯一的。由于油价的不断上涨,小F希望尽量减少行程中的燃油成本。车辆的油箱容量为400L,在起始点租车时,车内剩余油量为200L。每行驶1km消耗1L油。沿途设有多个加油站,小F可以在这些加油站补充燃油;此外,到达目标景点X还车的时候,需要保证车内剩余的油至少有200L。合理规划加油站的加油顺序和数量,计算最小化从青海湖到景点X的旅行成本(元)。

输入:

  • distance:从青海湖到景点X的总距离(km),距离最远不超过10000 km。
  • n:沿途加油站的数量 (1 <= n <= 100)
  • gas_stations:每个加油站的信息,包含两个非负整数 [加油站距离起始点的距离(km), 该加油站的油价(元/L)]

输出:

  • 最小化从青海湖到景点X的旅行成本(元)。如果无法到达景点X,或者到达景点X还车时油料剩余不足200L,则需要返回 -1 告诉小F这是不可能的任务。

解题思路

1. 判断可解情况

首先需要判断几种不可能完成任务的情况:

  • 如果没有加油站,显然无法完成任务。
  • 如果最后一个加油站距离终点太远(最后一个加油站位置 + 200L续航里程 > 400L总容量),也无法完成任务。

2. 动态规划设计

状态定义

dp[i][j] 表示:使用前i个加油站,当前油量为j时的最小花费。

转移方程

对于每个加油站i,有两种选择:

  1. 不在当前加油站加油dp[i][j] = dp[i-1][j] + dis

    • dis 是当前加油站到下一个加油站(或终点)的距离。
  2. 在当前加油站加油dp[i][j] = min(dp[i][j], dp[i][j-1] + price)

    • price 是当前加油站的油价。

边界条件
  • 初始状态:dp[0][200] = 0(起始油量200L,花费为0)。
  • 不可能状态:初始化为无穷大。

3. 实现细节

  • 对加油站按距离排序,方便计算相邻加油站之间的距离。
  • 需要特别处理第一个加油站到终点的距离。
  • 对于油箱容量超出400L的情况要排除。
  • 最终结果需要判断是否存在可行解(是否为无穷大)。

实现代码

普通动态规划

def solution(distance, n, gas_stations):
    # 如果没有加油站, 直接返回Impossible
    if n == 0:
        return -1
    gas_stations.sort(key=lambda x: x[0], reverse=True)

    # 如果最后一个加油站的距离+200, 剩余油量无法满足条件, 直接返回Impossible
    if gas_stations[n - 1][0] + 200 > 400:
        return -1

    # 按照距离排序, 距离为加油站与目的地的距离
    dp = [[float('inf')] * 401 for _ in range(n + 1)]
    dp[0][200] = 0
    flag = 1

    for i in range(1, n + 1):
        for j in range(0, 401):
            # 有些加油站不在 0-distance 之间, 直接跳过
            if gas_stations[i - 1][0] > distance:
                dp[i][200] = 0
                continue
            if flag == 1 or i == 1:
                dis = distance - gas_stations[i - 1][0]
                flag = 0
            else:
                dis = gas_stations[i - 2][0] - gas_stations[i - 1][0]
            # 更新dp[i][j]: i个加油站时, 油量为j, 最小花费
            # 本站不加油 or 本站加油
            if dis + j <= 400:    # 对于dis + j > 400的状态, 无法通过上一个加油站加油达成
                dp[i][j] = dp[i - 1][j + dis]
            if j != 0:
                dp[i][j] = min(dp[i][j], dp[i][j - 1] + gas_stations[i - 1][1])

    res = dp[n][gas_stations[n - 1][0] + 200]

    if res == float('inf'):
        return -1

    return int(res)

复杂度分析
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

空间优化

使用滚动数组可以进一步将空间复杂度降为O(1)。

def solution(distance, n, gas_stations):
    if n == 0:
        return -1
        
    # 按照距离排序(从远到近)
    gas_stations.sort(key=lambda x: x[0], reverse=True)
    
    # 检查是否能到达终点
    if gas_stations[n - 1][0] + 200 > 400:
        return -1
        
    # 使用滚动数组,只保存两行
    curr = [float('inf')] * 401
    prev = [float('inf')] * 401
    
    # 初始状态
    prev[200] = 0
    flag = 1
    
    for i in range(1, n + 1):
        # 复制前一个状态
        curr = [float('inf')] * 401
        
        # 如果加油站超出范围,跳过
        if gas_stations[i - 1][0] > distance:
            curr[200] = 0
            prev = curr
            continue
            
        # 计算距离
        if flag == 1 or i == 1:
            dis = distance - gas_stations[i - 1][0]
            flag = 0
        else:
            dis = gas_stations[i - 2][0] - gas_stations[i - 1][0]
            
        # 状态转移
        for j in range(401):
            # 不加油的情况
            if dis + j <= 400:
                curr[j] = prev[j + dis]
            
            # 加油的情况
            if j > 0:
                curr[j] = min(curr[j], curr[j - 1] + gas_stations[i - 1][1])
        
        # 更新prev数组
        prev = curr
    
    # 检查最终结果
    res = curr[gas_stations[n - 1][0] + 200]
    return -1 if res == float('inf') else int(res)