跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C/C++ 算法入门:一维动态规划实战解析

一维动态规划核心在于状态定义与转移方程的构建。通过泰波那契数、三步问题、最小花费爬楼梯及解码方法四个实例,演示如何从暴力递归优化至动态规划。内容涵盖滚动数组空间优化、取模运算处理溢出、虚拟节点处理边界等实战技巧,帮助开发者建立清晰的动规解题思路。

追风少年发布于 2026/3/22更新于 2026/10/2279 浏览
C/C++ 算法入门:一维动态规划实战解析

动态规划基础

动态规划(Dynamic Programming)的核心在于将复杂问题分解为重叠子问题,并通过存储中间结果避免重复计算。简单来说,它包含四个要素:状态定义、状态转移方程、初始条件以及状态存储(通常用数组或容器实现)。

在解决具体题目时,我们可以遵循以下思路:

  1. 状态表示:明确 dp[i] 的含义,例如'以 i 结尾的最大值'或'到达 i 位置的方法数'。
  2. 状态转移:推导 dp[i] 与之前状态的关系,这是最关键的一步。
  3. 初始化:处理边界情况,确保填表过程不越界。
  4. 填表顺序:根据依赖关系决定遍历方向,通常是从左到右或从下到上。
  5. 返回值:根据题意从 dp 表中提取最终结果。

下面通过四道经典例题,逐步演示如何应用这些步骤。

1. 第 N 个泰波那契数

这道题是典型的线性递推问题。泰波那契数列定义为 T(n) = T(n-1) + T(n-2) + T(n-3),初始值为 T(0)=0, T(1)=1, T(2)=1。

解题思路: 直接观察公式即可得到状态转移方程。由于计算 T(i) 只需要前三个值,我们可以使用滚动数组优化空间复杂度,无需维护整个 dp 数组。

代码实现:

class Solution {
public:
    int tribonacci(int n) {
        // 空间优化写法:使用三个变量代替 dp 数组
        int a = 0, b = 1, c = 1;
        int ret = 0;
        if (n == 0) return 0;
        if (n == 1 || n == 2) return 1;
        
        for (int i = 3; i <= n; i++) {
            ret = a + b + c;
            a = b;
            b = c;
            c = ret;
        }
        return ret;
    }
};

2. 三步问题

假设小孩每次可以走 1、2 或 3 步,求到达第 n 阶台阶有多少种方法。

分析: 要到达第 i 阶,最后一步可能来自 i-1、i-2 或 i-3。因此,dp[i] = dp[i-1] + dp[i-2] + dp[i-3]。这与泰波那契数类似,但需要注意取模运算以防止整数溢出。

代码实现:

class Solution {
public:
    int waysToStep(int n) {
        if (n == 1) return 1;
        if (n == 2) return 2;
        if (n == 3) return 4;
        
        vector<int> dp(n + 1);
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 4;
        
        for (int i = 4; i <= n; i++) {
            dp[i] = ((long long)dp[i - 1] + dp[i - 2] + dp[i - 3]) % 1000000007;
        }
        return dp[n];
    }
};

3. 使用最小花费爬楼梯

给定一个成本数组 cost,cost[i] 是从该台阶向上爬需要支付的费用。你可以从下标 0 或 1 开始,求到达楼顶的最小花费。

思路一:自底向上 定义 dp[i] 为到达第 i 层所需的最小花费。到达楼顶(即超过最后一个元素)需要从倒数第一或倒数第二层跳上来。

思路二:自顶向下 定义 dp[i] 为从第 i 层出发到达楼顶的最小花费。此时填表顺序应从右向左。

代码实现(自底向上):

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n + 1);
        // 初始化前两阶,因为可以从 0 或 1 开始,所以到达这两阶的花费为 0
        // 注意:这里 dp[i] 表示到达第 i 层的最小花费,不包含当前层的 cost
        // 另一种常见定义是 dp[i] 表示到达第 i 层并支付 cost[i] 后的总花费
        // 采用第二种定义更直观:
        dp[0] = cost[0];
        dp[1] = cost[1];
        
        for (int i = 2; i < n; i++) {
            dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];
        }
        // 最后一步可以直接从 n-1 或 n-2 跳到楼顶,不需要再付楼顶的 cost(因为楼顶没有 cost)
        return min(dp[n - 1], dp[n - 2]);
    }
};

4. 解码方法

给定只包含数字的字符串,将其解码为字母('A'-'Z' 对应 '1'-'26'),求有多少种解码方式。

关键点:

  1. 单个字符必须在 1-9 之间。
  2. 两个字符组合必须在 10-26 之间。
  3. 遇到 '0' 时需要特殊处理,因为它不能单独解码,必须与前一位结合。

技巧: 为了简化边界判断,可以在 dp 数组中多开一位作为虚拟节点。dp[0]=1 代表空字符串有一种解码方式(基准)。这样在处理 dp[2] 时,可以直接引用 dp[0] 而无需额外判断索引是否越界。

代码实现:

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        if (n == 0 || s[0] == '0') return 0;
        
        vector<int> dp(n + 1);
        dp[0] = 1; // 虚拟节点
        dp[1] = 1;
        
        for (int i = 2; i <= n; i++) {
            // 检查单个字符
            if (s[i - 1] != '0') {
                dp[i] += dp[i - 1];
            }
            // 检查两个字符组合
            int twoDigit = stoi(s.substr(i - 2, 2));
            if (twoDigit >= 10 && twoDigit <= 26) {
                dp[i] += dp[i - 2];
            }
        }
        return dp[n];
    }
};

总结

掌握动态规划的关键在于熟练运用状态定义和转移方程。在实际编码中,注意以下几点:

  • 边界处理:利用虚拟节点或多开空间简化逻辑。
  • 数据类型:涉及累加时注意使用 long long 防止溢出。
  • 空间优化:如果当前状态仅依赖前几个状态,可使用滚动变量替代数组。
  • 调试习惯:手动模拟小数据量的填表过程,验证逻辑是否正确。

通过不断练习上述类型的题目,能够建立起对动规问题的直觉,从而应对更复杂的变体。

目录

  1. 动态规划基础
  2. 1. 第 N 个泰波那契数
  3. 2. 三步问题
  4. 3. 使用最小花费爬楼梯
  5. 4. 解码方法
  6. 总结

更多推荐文章

查看全部
  • LLM 本地知识库构建:文档分割与向量化方案探讨
  • 高企年报自动化系统实战:用低代码替代 Excel 手工填报
  • Python 爬虫入门指南:从基础到 Scrapy 框架
  • C++ 多态底层实现原理与内存布局解析
  • Node.js+Vue 在线票务系统设计与功能实现
  • 鸿蒙电商购物车全栈项目:用户管理、商品列表与购物车实现
  • Llama-Factory 是否支持 FlashAttention 加速
  • Rust 异步 Web 框架 Axum 的深入原理与高级用法
  • Claude Git 集成:代码协作与版本管理实战指南
  • Java 二分查找算法题目练习
  • C++ string 类基础刷题实战:输入、查找与反转
  • 国内直连 AI 绘画工具深度实践与部署指南
  • Mac mini 静音低功耗配置:OpenClaw 卧室运行指南
  • Meta-Llama-3-8B-Instruct 本地部署与对话全流程实战
  • DeepSeek-R1 大模型基于 MS-Swift 框架部署与微调实践
  • Stable Diffusion 本地部署与 Automatic1111 WebUI 使用指南
  • 计算机视觉高级应用与前沿技术实战解析
  • 数据结构:堆的实现与应用
  • 基于 UDP 协议的群聊服务器开发(C++)
  • Python 数据可视化实战:Matplotlib 基础与进阶

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online