SWE · Coding

TikTok 两步跳跃最小成本:从下标 0 到最后一格的 DP

按明确起终点计算访问成本,区别于可以从第1阶开始并跳出数组的标准楼梯版本。

题目重点:按明确起终点计算访问成本,区别于可以从第1阶开始并跳出数组的标准楼梯版本。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练必须访问下标0和n−1,每访问一个格子支付该格cost;空数组约定0,单格支付自身。
  • 只向前走1/2步,允许负cost;没有回边,不产生负环。
  • 若正式允许从1开始或终点在数组外,需要更换初值/答案,不能只改函数名字。

解题路线

  1. dp[i]表示必须落在i时的最小累计成本,dp[0]=cost[0],dp[1]=cost[0]+cost[1]。这两个初值来自强制起点,不是“从0或1任选”的楼梯模板。
  2. i≥2时最后一步只能从i−1或i−2到来,因此dp[i]=cost[i]+min(dp[i−1],dp[i−2])。所有合法路径均由两类前驱覆盖,没有遗漏。
  3. 正确性:任意到i的最优路径若前缀不是其前驱最优路径,可替换前缀得到更低成本,矛盾;所以选两个最优前驱中的较小值并支付当前cost即可。
  4. 只保留最近两项,循环时同时更新避免覆盖旧dp[i−2];按下标访问输入,不能创建cost[2:]切片后仍声称额外空间O(1)。
  5. 负cost也不影响DAG最短路递推,跳过一个负cost节点可能变差,不能用每步贪心选最低下一格。强制最后一格时答案dp[n−1],不是最后两项min。
  6. 要返回路径,另保存前驱,遇并列定义偏好1步或2步。若只返回值,保留整个dp表没有必要。
  7. 扩展允许跳集合J时对所有合法前驱取min,O(n|J|);超长跳、禁止落点或取模计数各是新问题,不应套当前两个状态而不重新核转移。

手动走一遍

[5,100,1]必须从0到2,可一步跨度2,总6。[5,−10,1]逐格走总−4,比跳过负成本的6更小。[10,15]本合同支付25;可任选起点的楼梯版可能给10或15,说明初值必须澄清。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

"""Start at index 0, finish at n-1, take 1/2 steps and pay every visited cost.
Costs may be negative; empty input returns zero by training convention.
"""


def minimum_path_cost(costs):
    if not costs:
        return 0
    if len(costs) == 1:
        return costs[0]
    previous_two = costs[0]
    previous_one = costs[0] + costs[1]
    for i in range(2, len(costs)):
        previous_two, previous_one = previous_one, costs[i] + min(previous_two, previous_one)
    return previous_one

复杂度

O(n)时间、O(1)额外空间,只返回值;返回路径需要O(n)前驱/输出。大整数加比较按数值位数另计。

易错点与追问

容易失分的边界

  • 套楼梯初值允许从1开始。
  • 答案取min最后两项而不是强制终点。
  • 负cost用局部贪心。
  • 切片复制却声称常量工作空间。

继续深挖

  • 终点在数组外如何修改?
  • 怎样重建最优路径?
  • 跳跃上限K怎样优化?
  • 若某些格子禁止落点怎么表达不可达?
✓已复制微信号!