第233题 小U的地砖之旅

79 阅读3分钟

问题描述

小U有 n 块地砖,她需要从第一块地砖走到第 n 块地砖。走到第 i 块地砖需要消耗 aia_i 的体力值。每次,小U可以选择向前走一步或者向前走两步,目的是消耗最少的体力值走到第 n 块地砖。

请你帮忙计算小U走到第 n 块地砖时消耗的最小体力值。

问题分析

小U需要从第 1 块地砖走到第 nn 块地砖,每一步都需要消耗一定的体力值 a[i]a[i]。每次可以选择走 1 步或 2 步,目标是让体力值的总消耗最小。

问题的本质

小U需要在每一步中选择是走 1 步还是走 2 步,每种选择都伴随着不同的体力值消耗。她的目标是找到一种“全局最优”的走法,即在所有可能的路径中,选出体力值消耗最少的那一种。

问题的特点

  • 每块地砖只能走一次(不会回退)。
  • 走到第 ii 块地砖时,小U的选择只与前一块地砖(或前两块地砖)有关,与更早的状态无关。这种性质使得问题可以用 动态规划 的方式解决。

动态规划分析

  1. 状态定义
    用一个数组 dp[i]dp[i] 表示小U走到第 ii 块地砖时消耗的最小体力值。

  2. 状态转移方程

    • 小U可以从第 i1i-1 块地砖跳到第 ii 块,这需要额外消耗 a[i]a[i] 的体力值,此时消耗为 dp[i1]+a[i]dp[i-1] + a[i]
    • 小U也可以从第 i2i-2 块地砖跳到第 ii 块,这需要额外消耗 a[i]a[i] 的体力值,此时消耗为 dp[i2]+a[i]dp[i-2] + a[i]
      因此,状态转移方程为:

    dp[i]=min(dp[i1]+a[i],dp[i2]+a[i])dp[i] = \min(dp[i-1] + a[i], dp[i-2] + a[i])

  3. 初始条件

    • 小U站在第 1 块地砖时,不需要消耗体力值,dp[0]=0dp[0] = 0
    • 小U走到第 2 块地砖的最小消耗就是第 2 块地砖的体力值,dp[1]=a[1]dp[1] = a[1]
  4. 目标
    dp[n1]dp[n-1],即走到第 nn 块地砖时的最小体力值。

代码实现

def solution(n: int, a: list) -> int:
    if n == 1:
        return 0  # 只有一块地砖时,直接返回0
    
    # 初始化 dp 数组,dp[i] 表示走到第 i 块地砖的最小消耗
    dp = [float('inf')] * n
    dp[0] = 0  # 起点不消耗体力
    dp[1] = a[1]  # 第二块地砖消耗的体力
    
    # 动态规划迭代
    for i in range(2, n):
        dp[i] = min(dp[i-1] + a[i], dp[i-2] + a[i])
    
    # 返回走到第 n 块地砖的最小消耗
    return dp[n-1]

示例详解

示例 1

输入

  • n=5n = 5
  • a=[0,3,2,1,0]a = [0, 3, 2, 1, 0]

我们需要从第 1 块地砖走到第 5 块地砖,按以下步骤求解:

  1. 初始化

    • dp[0]=0dp[0] = 0:起点不消耗体力。
    • dp[1]=a[1]=3dp[1] = a[1] = 3:直接走到第 2 块地砖消耗体力为 3。
  2. 计算 dp[2]

    • dp[1]dp[1] 跳到 dp[2]dp[2]:消耗 dp[1]+a[2]=3+2=5dp[1] + a[2] = 3 + 2 = 5
    • dp[0]dp[0] 跳到 dp[2]dp[2]:消耗 dp[0]+a[2]=0+2=2dp[0] + a[2] = 0 + 2 = 2
      因此,dp[2]=min(5,2)=2dp[2] = \min(5, 2) = 2
  3. 计算 dp[3]

    • dp[2]dp[2] 跳到 dp[3]dp[3]:消耗 dp[2]+a[3]=2+1=3dp[2] + a[3] = 2 + 1 = 3
    • dp[1]dp[1] 跳到 dp[3]dp[3]:消耗 dp[1]+a[3]=3+1=4dp[1] + a[3] = 3 + 1 = 4
      因此,dp[3]=min(3,4)=3dp[3] = \min(3, 4) = 3
  4. 计算 dp[4]

    • dp[3]dp[3] 跳到 dp[4]dp[4]:消耗 dp[3]+a[4]=3+0=3dp[3] + a[4] = 3 + 0 = 3
    • dp[2]dp[2] 跳到 dp[4]dp[4]:消耗 dp[2]+a[4]=2+0=2dp[2] + a[4] = 2 + 0 = 2
      因此,dp[4]=min(3,2)=2dp[4] = \min(3, 2) = 2

最终结果为 dp[4]=2dp[4] = 2

示例 2:

  • 输入:n=4n = 4, a=[0,5,6,0]a = [0, 5, 6, 0]

  • 计算过程:

    • dp[1]=5dp[1] = 5
    • dp[2]=min(dp[1]+a[2],dp[0]+a[2])=min(5+6,0+6)=6dp[2] = \min(dp[1] + a[2], dp[0] + a[2]) = \min(5 + 6, 0 + 6) = 6
    • dp[3]=min(dp[2]+a[3],dp[1]+a[3])=min(6+0,5+0)=5dp[3] = \min(dp[2] + a[3], dp[1] + a[3]) = \min(6 + 0, 5 + 0) = 5
  • 输出:55

示例 3:

  • 输入:n=6n = 6, a=[0,1,2,3,1,0]a = [0, 1, 2, 3, 1, 0]

  • 计算过程:

    • dp[1]=1dp[1] = 1
    • dp[2]=min(dp[1]+a[2],dp[0]+a[2])=min(1+2,0+2)=2dp[2] = \min(dp[1] + a[2], dp[0] + a[2]) = \min(1 + 2, 0 + 2) = 2
    • dp[3]=min(dp[2]+a[3],dp[1]+a[3])=min(2+3,1+3)=4dp[3] = \min(dp[2] + a[3], dp[1] + a[3]) = \min(2 + 3, 1 + 3) = 4
    • dp[4]=min(dp[3]+a[4],dp[2]+a[4])=min(4+1,2+1)=3dp[4] = \min(dp[3] + a[4], dp[2] + a[4]) = \min(4 + 1, 2 + 1) = 3
    • dp[5]=min(dp[4]+a[5],dp[3]+a[5])=min(3+0,4+0)=3dp[5] = \min(dp[4] + a[5], dp[3] + a[5]) = \min(3 + 0, 4 + 0) = 3
  • 输出:33


复杂度分析

  1. 时间复杂度

    • 需要对长度为 nn 的数组进行一次遍历,时间复杂度为 O(n)O(n)
  2. 空间复杂度

    • 使用了一个长度为 nn 的数组 dpdp,空间复杂度为 O(n)O(n)
    • 如果进一步优化,利用滚动数组,只需保存最近两个状态,空间复杂度可以降为 O(1)O(1)

优化版本代码

def solution(n: int, a: list) -> int:
    if n == 1:
        return 0
    
    # 只保存最近两个状态,优化空间复杂度
    prev2 = 0
    prev1 = a[1]
    
    for i in range(2, n):
        curr = min(prev1 + a[i], prev2 + a[i])
        prev2, prev1 = prev1, curr
    
    return prev1

总结

本题使用动态规划求解,通过状态转移方程计算出每一步的最优选择。在实现中,我们还可以通过滚动数组优化空间复杂度,是一道典型的动态规划入门问题。