首页 > 其他分享 >一步步讲解:如何通过动态规划解决「爬楼梯最低花费」问题

一步步讲解:如何通过动态规划解决「爬楼梯最低花费」问题

时间:2024-10-17 21:17:27浏览次数:3  
标签:爬楼梯 min 花费 一步步 cost 讲解 台阶 100 dp

引言

在面对算法问题时,初学者常常会感到迷茫,不知道从何下手。尤其是像「爬楼梯最低花费」这样的动态规划问题,虽然看起来简单,但如果没有掌握合适的思维框架,往往难以快速找到解题的突破口。动态规划的核心思想是将问题分解为若干个子问题,通过递推的方式找到最优解,而理解这一点是解题的关键。

在这篇博客中,我将带你逐步构建出解决「爬楼梯最低花费」问题的完整动态规划思路。我们会从如何分析问题、构建状态到编写代码,全面展示解题的全过程,并通过优化后的代码,提升算法的效率。希望通过这一讲解,能够帮助你更好地理解动态规划的思维方式,让这类问题变得不再困难。

问题描述

给定一个整数数组 cost,其中 cost[i] 表示从第 i 个台阶向上爬需要支付的费用。每次你可以选择向上爬一个或两个台阶,你可以从第0层或第1层开始,目标是计算爬到楼梯顶端的最低花费。

举例来说:

  • 示例 1:

    输入: cost = [10, 15, 20]
    输出: 15
    

    从第 1 层开始,支付 15 到达楼梯顶部,总花费为 15。

  • 示例 2:

    输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
    输出: 6
    

    从第 0 层开始,支付 1 后一步一步跳到顶部,总花费为 6。

746. 使用最小花费爬楼梯 - 力扣(LeetCode)

动态规划解题思路

我们可以将这个问题抽象为一个最优子结构问题,即:

  • 要到达第 i 个台阶,我们有两种选择:从第 i-1 个台阶或者从第 i-2 个台阶跳上来,选择这两者中花费较小的路径加上当前台阶的花费 cost[i]

因此,这里我们使用动态规划的思想来解决这个问题。动态规划的核心在于分解问题:当前的最优解是依赖于前面状态的最优解的。

1. 定义状态

我们可以定义一个数组 dp,其中 dp[i] 表示到达第 i 个台阶所需要的最低花费。我们的目标是找到 dp 数组的最优解。

首先我们明确以下几点:

  • 可以从第 0 层或第 1 层开始,因此 dp[0] = cost[0]dp[1] = cost[1]
  • 从第 2 层开始,我们需要从前面的两个台阶 i-1i-2 跳上来,因此状态转移方程是:

d p [ i ] = min ⁡ ( d p [ i − 1 ] , d p [ i − 2 ] ) + c o s t [ i ] dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] dp[i]=min(dp[i−1],dp[i−2])+cost[i]

这意味着到达第 i 个台阶,我们可以选择从 i-1i-2 来,根据这两者中哪一个代价更小,再加上当前台阶的花费。

2. 初始条件

我们从第 0 层或第 1 层开始爬楼梯,所以 dp[0]dp[1] 是已知的,分别等于 cost[0]cost[1]

3. 最终解

当我们到达楼梯的最后时,楼梯的顶部可以从倒数第二层或最后一层上去。因此,最终的最小花费就是:

result = min ⁡ ( d p [ n − 1 ] , d p [ n − 2 ] ) \text{result} = \min(dp[n-1], dp[n-2]) result=min(dp[n−1],dp[n−2])

即:到达最后一个台阶或者倒数第二个台阶的最小花费。

动态规划代码实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int minCostClimbingStairs(vector<int>& cost) {
    int n = cost.size();
    vector<int> dp(n);
    
    // 初始化第0层和第1层的最小花费
    dp[0] = cost[0];
    dp[1] = cost[1];

    // 通过状态转移方程更新dp数组
    for (int i = 2; i < n; ++i) {
        dp[i] = min(dp[i-1], dp[i-2]) + cost[i];
    }

    // 返回到达楼梯顶部的最小花费
    return min(dp[n-1], dp[n-2]);
}

int main() {
    vector<int> cost1 = {10, 15, 20};
    vector<int> cost2 = {1, 100, 1, 1, 1, 100, 1, 1, 100, 1};

    cout << "最低花费(示例1): " << minCostClimbingStairs(cost1) << endl;
    cout << "最低花费(示例2): " << minCostClimbingStairs(cost2) << endl;

    return 0;
}

代码讲解

  • 我们首先定义了一个 dp 数组,存储到达每个台阶的最小花费。
  • 初始阶段,直接把 dp[0]dp[1] 初始化为 cost[0]cost[1],因为这是最初的起点。
  • 然后从第2层台阶开始,依次根据状态转移方程更新 dp 数组的值。
  • 最后,我们只需要返回 dp[n-1]dp[n-2] 的最小值,因为我们可以从这两层中的任意一层到达楼梯的顶部。

示例分析

示例 1:

输入:cost = [10, 15, 20]

  • dp[0] = 10
  • dp[1] = 15
  • 计算 dp[2] = min(dp[1], dp[0]) + cost[2] = min(15, 10) + 20 = 30

结果为 min(dp[1], dp[2]) = min(15, 30) = 15,输出为 15。

示例 2:

输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]

  • dp[0] = 1
  • dp[1] = 100
  • 计算:
    • dp[2] = min(dp[1], dp[0]) + cost[2] = min(100, 1) + 1 = 2
    • dp[3] = min(dp[2], dp[1]) + cost[3] = min(2, 100) + 1 = 3
    • dp[4] = min(dp[3], dp[2]) + cost[4] = min(3, 2) + 1 = 3
    • 继续类似地计算到最后。

结果为 min(dp[9], dp[8]) = min(6, 104) = 6,输出为 6。

优化思路:减少空间复杂度

在上面的解法中,我们用了 dp 数组保存每一个台阶的最小花费,但是我们其实只需要保留前两个台阶的最小花费,因此可以将空间复杂度从 O(n) 降低到 O(1)

优化后的代码如下:

int minCostClimbingStairsOptimized(vector<int>& cost) {
    int n = cost.size();
    int prev1 = cost[1], prev2 = cost[0];

    for (int i = 2; i < n; ++i) {
        int curr = min(prev1, prev2) + cost[i];
        prev2 = prev1;
        prev1 = curr;
    }

    return min(prev1, prev2);
}

总结

在这篇博客中,我们通过动态规划的思路,逐步构建了「爬楼梯最低花费」问题的解法。通过定义状态 dp[i] 表示到达第 i 个台阶的最小花费,我们可以依赖前两个台阶的最优解来求出当前台阶的最优解。最后通过状态转移公式,得到从底部爬到楼梯顶部的最小花费。

标签:爬楼梯,min,花费,一步步,cost,讲解,台阶,100,dp
From: https://blog.csdn.net/qq_22841387/article/details/142996210

相关文章

  • 【视频讲解】共享单车使用量预测:RNN, LSTM,GRU循环神经网络和传统机器学习
    全文链接:https://tecdat.cn/?p=37899原文出处:拓端数据部落公众号分析师:XuyanReng 随着城市化进程的加速,共享单车作为一种绿色、便捷的出行方式,在城市交通中扮演着日益重要的角色。准确预测共享单车的使用量对于优化资源配置、提高运营效率以及满足用户需求具有关键意义。一......
  • SpringBoot健康检查机制讲解与实现
    目录前言什么是SpringBoot健康检查如何实现SpringBoot健康检查通过官方模块SpringBootActuator什么是SpringBootActuator快速入门SpringBootActuator引入依赖添加.yml文件配置运行项目访问健康检查地址重要端点解析/health端点注意/loggers端点/info......
  • 基于ssm+vue.js的二手车交易网站附带文章源码部署视频讲解等
    文章目录前言详细视频演示具体实现截图核心技术介绍后端框架SSM前端框架Vue持久层框架MyBaits为什么选择我代码参考数据库参考测试用例参考源码获取前言......
  • k8s部署Kafka集群超详细讲解
    准备部署环境Kubernetes集群信息NAMEVERSIONk8s-masterv1.29.2k8s-node01v1.29.2k8s-node02v1.29.2Kafka:3.7.1版本Zookeeper:3.6.3版本准备StorageClass#kubectlgetscNAMEPROVISIONERRECLAIMPOLICYVOLUMEBINDINGMODEALLOWVOLUMEEXPAN......
  • 旁路电容和去耦合什么作用:【图文讲解】
    1:旁路和去耦旁路电容:BypassCapacitor去耦电容:DecouplingCapacitor这两个概念在电路中是常见的,但是真正理解起来并不容易。我们先回到英文语境里,来看它们的意思。Bypass:在英语中有抄小路的意思,在电路中也这个意思,如下图所示:couple:在英语中是一对的意思,引申为配对、耦合......
  • 网上纪念馆(源码+文档+部署+讲解)
    网上纪念馆是成品商业化项目,系统可基于源码二开。系统概述是一款为个人和家族提供线上祭祀、纪念服务的平台,涵盖了纪念馆创建、供奉记录、祭品管理、背景音乐设置等功能,让用户随时随地缅怀先人。详细功能介绍:纪念馆管理:支持创建不同类型的纪念馆(名人纪念馆、普通纪念馆......
  • 电子病历系统(源码+文档+部署+讲解)
    电子病历系统是成品商业化项目,系统可基于源码二开。系统概述系统功能总结患者中心病历模版工作台:提供可自定义的病历模版,方便医生快速生成病历。预约管理:患者可在线预约就诊,系统自动生成预约记录。诊所管理:患者可查询就诊记录、查看诊所信息等。回访管理:系统可对......
  • 售票系统(源码+文档+部署+讲解)
    售票系统是成品商业化项目,系统可基于源码二开。系统概述票务管理系统是一款为游乐园量身定制的综合性管理平台,涵盖了从门票销售、检票管理到财务统计等全流程的业务,旨在提高运营效率,提升游客体验。详细功能介绍:票务管理:支持单票、套票销售,提供手工出票、检票管理等功......
  • Charger IC原理讲解与个人理解
    ChargerIC原理讲解与个人理解ChargerICChargerIC的主要功能包括:ChargerIC的共同点:ChargerIC典型应用图OVPOVP的工作原理:OVP的实现方法:ChargerICChargerIC(充电集成电路)是一种专门用于管理电池充电的集成电路。它的主要功能是控制电池的充电过程,以确保安全、......
  • 基于SpringBoot+Vue+uniapp的互助学习小程序的详细设计和实现(源码+lw+部署文档+讲解
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......