刷题实践:优化青海湖至景点X的租车路线成本 | 豆包MarsCode AI刷题

167 阅读6分钟

前言

本人算法能力较差,且第二次写算法题解,请多多指教


问题描述

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

小F需要你帮助他计算,如果合理规划加油站的加油顺序和数量,最小化从青海湖到景点X的旅行成本(元)。

输入:

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

输出:

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

测试样例

样例1:

输入:distance = 500, n = 4, gas_stations = [[100, 1], [200, 30], [400, 40], [300, 20]]
输出:4300

样例2:

输入:distance = 1000, n = 3, gas_stations = [[300, 25], [600, 35], [900, 5]]
输出:-1

样例3:

输入:distance = 200, n = 2, gas_stations = [[100, 50], [150, 45]]
输出:9000

样例4:

输入:distance = 700, n = 5, gas_stations = [[100, 10], [200, 20], [300, 30], [400, 40], [600, 15]]
输出:9500

样例5:

输入:distance = 50, n = 1, gas_stations = [[25, 100]]
输出:5000

动态规划

什么是动态规划

动态规划是一种通过将问题分解为子问题,并通过记忆化存储(避免重复计算)来优化求解过程的算法思想。它主要适用于解决具有最优子结构重叠子问题的优化问题。

核心在于:

  1. 将复杂问题分解为更小的子问题,通过子问题的解来构造原问题的解。
  2. 记忆化存储每个子问题的解,避免重复计算,提高效率。

为什么选择动态规划

1. 问题的多阶段性
  • 小F从起点出发到终点,需要经过多个加油站,每个加油站的油价不同,行驶到下一个加油站的油量需求也不同。
  • 在每一个加油站,小F需要决策是否加油,以及加多少油,以尽可能减少燃油成本。
  • 每次的决策都会影响后续的状态(如剩余油量、到达下一站的可能性和花费)。
2. 状态的依赖性
  • 当前加油站的状态(如油量和总花费)依赖于前一个加油站的状态。
  • 在到达某个加油站时,有多种可能的油量状态,而这些状态会直接影响小F是否能顺利到达终点。
3. 问题的最优子结构
  • 最优子结构是动态规划的一个重要特性,指问题的最优解可以通过子问题的最优解递推得到。
  • 本题中,小F到达某一加油站时的最小花费,只依赖于上一个加油站的状态(剩余油量和花费)。换句话说,到达某一站的最优解,能够通过之前各站的最优解递推计算出来。
4. 状态的重复性
  • 小F在行驶过程中,可能以相同的剩余油量、多种路径到达同一个加油站,但此时只需要记录最小花费的路径,忽略次优解。
  • 如果用暴力法计算,可能会重复计算大量相似的状态,导致计算复杂度过高。
  • 动态规划通过状态表记录每个状态的最优解,从而避免了不必要的重复计算。
算法步骤

1. 初始化问题条件

  • 确定油箱容量(400L)和初始油量(200L)。
  • 确保每段路程可以在油箱的最大容量下行驶。

2. 动态规划定义

  • 定义 dp[i][j] 表示到达第 i 个加油站时,油箱剩余 j 升油的最小成本。

3. 转移方程

  • 如果从前一个加油站 kkk 行驶到当前站 iii 时,油量足够,且可通过加油覆盖需要的油量:

    dp[i][j]=min(dp[i][j],dp[k][剩余油量]+加油花费)
    

4. 边界条件

  • 初始状态:dp[0][200] = 0,表示从起点出发,初始油量为200L时的花费为0。

5. 判断终点条件

  • 确保终点时油量满足要求(至少200L)。
  • 如果无法满足,则返回 -1

代码

import sys

def solution(distance, n, gasStations):

    maxCapacity = 400  # 油箱最大容量
    inf = sys.maxsize  # 表示一个非常大的值,用于初始化不可达的状态

    # 按照加油站距离从起点的远近进行升序排序
    gasStations.sort(key=lambda x: x[0])

    # 计算每两个加油站之间的距离
    # dis[i] 表示从加油站 i 到加油站 i+1 的距离
    dis = [gasStations[0][0]]  # 起点到第一个加油站的距离
    for i in range(1, len(gasStations)):
        dis.append(gasStations[i][0] - gasStations[i - 1][0])

    # 动态规划数组 dp[i][j]
    # 表示到达第 i 个加油站时,油箱剩余 j 升油的最小花费
    dp = [[inf] * (maxCapacity + 1) for _ in range(n + 1)]
    dp[0][200] = 0  # 初始状态:起点油箱容量为 200L,花费为 0

    # 动态规划计算每个状态的最小花费
    for i in range(1, n + 1):  # 遍历每个加油站(从第 1 个到第 n 个)
        for currentFuel in range(maxCapacity + 1):  # 当前站剩余油量
            for previousFuel in range(maxCapacity + 1):  # 前一站剩余油量
                # 前往当前站所需油量
                fuelNeeded = dis[i - 1]
                # 判断从上一个加油站到当前站是否可行
                if previousFuel >= fuelNeeded and currentFuel >= 0:
                    # 计算从上一个站补充到当前油量所需的花费
                    fuelToRefill = currentFuel + fuelNeeded - previousFuel
                    if fuelToRefill >= 0:  # 补充的油量不能为负数
                        # 更新到当前站的最小花费
                        dp[i][currentFuel] = min(dp[i][currentFuel], dp[i - 1][previousFuel] + fuelToRefill * gasStations[i - 1][1])

    # 判断是否能够到达终点
    # remaining_fuel 表示到达终点时需要的剩余油量
    remaining_fuel = 200 + distance - gasStations[n - 1][0]
    if remaining_fuel > maxCapacity or remaining_fuel < 0:
        # 无法满足终点要求(油量不足或超过油箱容量)
        return -1

    # 从满足终点剩余油量的状态中,找到最小的花费
    min_cost = inf
    for fuel in range(remaining_fuel, maxCapacity + 1):  # 枚举所有满足条件的油量
        min_cost = min(min_cost, dp[n][fuel])

    # 如果结果仍为 inf,说明无法到达终点
    return -1 if min_cost == inf else min_cost

豆包MarsCode AI 刷题的使用

在本题中,我用它来检查代码,以分析代码过程以及结果是否符合。

image.png