1. 动态规划入门从斐波那契到路径问题动态规划Dynamic Programming是算法设计中一种非常重要的思想它通过将复杂问题分解为子问题来降低计算复杂度。很多初学者第一次接触动态规划时往往会被其抽象的概念所困扰。今天我们就从最基础的斐波那契数列开始逐步深入到更复杂的路径问题帮助大家建立起对动态规划的直观理解。动态规划的核心在于记忆化和状态转移。想象你是一名快递员需要规划最优配送路线。如果你每次配送都重新计算所有可能的路线效率会非常低下。而动态规划的思想就是记住已经计算过的路线下次遇到相同的配送需求时直接使用之前的结果。2. 斐波那契类问题解析2.1 泰波那契数列问题泰波那契数列是斐波那契数列的扩展版本定义如下 T0 0, T1 1, T2 1 Tn Tn-1 Tn-2 Tn-3 当 n ≥ 32.1.1 基础解法最直观的解法是递归但递归存在大量重复计算时间复杂度为O(3^n)效率极低。动态规划通过存储中间结果来优化public int tribonacci(int n) { if(n 0) return 0; if(n 1 || n 2) return 1; int[] dp new int[n1]; dp[0] 0; dp[1] dp[2] 1; for(int i 3; i n; i) { dp[i] dp[i-1] dp[i-2] dp[i-3]; } return dp[n]; }这里我们创建了一个dp数组来存储每个位置的泰波那契数。时间复杂度降为O(n)空间复杂度也是O(n)。2.1.2 空间优化观察发现我们只需要前三个值就能计算当前值因此可以优化空间public int tribonacci(int n) { if(n 0) return 0; if(n 1 || n 2) return 1; int a 0, b 1, c 1, d 0; for(int i 3; i n; i) { d a b c; a b; b c; c d; } return d; }这样空间复杂度降为O(1)这种技巧称为滚动数组。2.1.3 记忆化搜索另一种思路是递归记忆化int[] memory; public int tribonacci(int n) { memory new int[n1]; return dfs(n); } private int dfs(int n) { if(n 0) return 0; if(n 1 || n 2) return 1; if(memory[n] ! 0) return memory[n]; memory[n] dfs(n-1) dfs(n-2) dfs(n-3); return memory[n]; }这种方法结合了递归的直观性和动态规划的高效性。2.2 三步问题三步问题是泰波那契数列的变种一个人可以一次迈1、2或3步问到达第n阶有多少种走法。2.2.1 动态规划解法状态转移方程与泰波那契数列类似public int waysToStep(int n) { if(n 1) return 1; if(n 2) return 2; if(n 3) return 4; long[] dp new long[n1]; dp[1] 1; dp[2] 2; dp[3] 4; int mod 1000000007; for(int i 4; i n; i) { dp[i] (dp[i-1] dp[i-2] dp[i-3]) % mod; } return (int)dp[n]; }注意这里使用了long类型和取模运算防止整数溢出。2.2.2 空间优化版同样可以优化空间public int waysToStep(int n) { if(n 1) return 1; if(n 2) return 2; if(n 3) return 4; int a 1, b 2, c 4, d 0; int mod 1000000007; for(int i 4; i n; i) { d (a b) % mod; d (d c) % mod; a b; b c; c d; } return d; }2.3 最小花费爬楼梯这个问题要求计算爬到楼梯顶部的最小花费每次可以爬1或2个台阶。2.3.1 正向思考解法定义dp[i]为到达第i阶的最小花费public int minCostClimbingStairs(int[] cost) { int n cost.length; int[] dp new int[n1]; for(int i 2; i n; i) { dp[i] Math.min(dp[i-1] cost[i-1], dp[i-2] cost[i-2]); } return dp[n]; }2.3.2 逆向思考解法也可以从后往前思考dp[i]表示从第i阶到顶楼的最小花费public int minCostClimbingStairs(int[] cost) { int n cost.length; int[] dp new int[n]; dp[n-1] cost[n-1]; dp[n-2] cost[n-2]; for(int i n-3; i 0; i--) { dp[i] cost[i] Math.min(dp[i1], dp[i2]); } return Math.min(dp[0], dp[1]); }2.4 解码方法这个问题要求计算数字字符串可以解码为字母字符串的方法数。2.4.1 动态规划解法public int numDecodings(String s) { int n s.length(); int[] dp new int[n1]; dp[0] 1; dp[1] s.charAt(0) 0 ? 0 : 1; for(int i 2; i n; i) { int oneDigit Integer.parseInt(s.substring(i-1, i)); int twoDigits Integer.parseInt(s.substring(i-2, i)); if(oneDigit 1) { dp[i] dp[i-1]; } if(twoDigits 10 twoDigits 26) { dp[i] dp[i-2]; } } return dp[n]; }这里使用了虚拟节点dp[0]来简化边界条件的处理。3. 路径类问题解析3.1 不同路径问题3.1.1 基础版本在一个m×n的网格中从左上角到右下角有多少条唯一路径。public int uniquePaths(int m, int n) { int[][] dp new int[m][n]; // 初始化第一行和第一列 for(int i 0; i m; i) dp[i][0] 1; for(int j 0; j n; j) dp[0][j] 1; for(int i 1; i m; i) { for(int j 1; j n; j) { dp[i][j] dp[i-1][j] dp[i][j-1]; } } return dp[m-1][n-1]; }3.1.2 空间优化可以优化为一维数组public int uniquePaths(int m, int n) { int[] dp new int[n]; Arrays.fill(dp, 1); for(int i 1; i m; i) { for(int j 1; j n; j) { dp[j] dp[j-1]; } } return dp[n-1]; }3.2 带障碍物的不同路径网格中某些位置有障碍物无法通过。public int uniquePathsWithObstacles(int[][] obstacleGrid) { int m obstacleGrid.length; int n obstacleGrid[0].length; int[][] dp new int[m][n]; // 初始化第一行和第一列 dp[0][0] obstacleGrid[0][0] 1 ? 0 : 1; for(int i 1; i m; i) { dp[i][0] (obstacleGrid[i][0] 1) ? 0 : dp[i-1][0]; } for(int j 1; j n; j) { dp[0][j] (obstacleGrid[0][j] 1) ? 0 : dp[0][j-1]; } for(int i 1; i m; i) { for(int j 1; j n; j) { if(obstacleGrid[i][j] 1) { dp[i][j] 0; } else { dp[i][j] dp[i-1][j] dp[i][j-1]; } } } return dp[m-1][n-1]; }3.3 珠宝的最高价值在一个m×n的网格中每个格子有不同价值的珠宝求从左上角到右下角能收集的最大价值。public int maxValue(int[][] grid) { int m grid.length; int n grid[0].length; int[][] dp new int[m][n]; dp[0][0] grid[0][0]; // 初始化第一行和第一列 for(int i 1; i m; i) { dp[i][0] dp[i-1][0] grid[i][0]; } for(int j 1; j n; j) { dp[0][j] dp[0][j-1] grid[0][j]; } for(int i 1; i m; i) { for(int j 1; j n; j) { dp[i][j] Math.max(dp[i-1][j], dp[i][j-1]) grid[i][j]; } } return dp[m-1][n-1]; }3.4 下降路径最小和在一个n×n的方形网格中找出从第一行任意位置开始到最下面一行的最小路径和每次可以向下、向左下或向右下移动。public int minFallingPathSum(int[][] matrix) { int n matrix.length; int[][] dp new int[n][n]; // 初始化第一行 for(int j 0; j n; j) { dp[0][j] matrix[0][j]; } for(int i 1; i n; i) { for(int j 0; j n; j) { dp[i][j] dp[i-1][j]; // 从正上方下来 if(j 0) { dp[i][j] Math.min(dp[i][j], dp[i-1][j-1]); // 从左上方下来 } if(j n-1) { dp[i][j] Math.min(dp[i][j], dp[i-1][j1]); // 从右上方下来 } dp[i][j] matrix[i][j]; } } // 找出最后一行中的最小值 int minSum dp[n-1][0]; for(int j 1; j n; j) { minSum Math.min(minSum, dp[n-1][j]); } return minSum; }3.5 最小路径和在一个m×n的网格中找出从左上角到右下角的路径使得路径上的数字总和最小。public int minPathSum(int[][] grid) { int m grid.length; int n grid[0].length; int[][] dp new int[m][n]; dp[0][0] grid[0][0]; // 初始化第一行和第一列 for(int i 1; i m; i) { dp[i][0] dp[i-1][0] grid[i][0]; } for(int j 1; j n; j) { dp[0][j] dp[0][j-1] grid[0][j]; } for(int i 1; i m; i) { for(int j 1; j n; j) { dp[i][j] Math.min(dp[i-1][j], dp[i][j-1]) grid[i][j]; } } return dp[m-1][n-1]; }3.6 地下城游戏这是一个典型的逆向动态规划问题。我们需要从终点反向计算每个位置需要的最小初始健康点数。public int calculateMinimumHP(int[][] dungeon) { int m dungeon.length; int n dungeon[0].length; int[][] dp new int[m][n]; // 初始化终点 dp[m-1][n-1] Math.max(1, 1 - dungeon[m-1][n-1]); // 初始化最后一行和最后一列 for(int i m-2; i 0; i--) { dp[i][n-1] Math.max(1, dp[i1][n-1] - dungeon[i][n-1]); } for(int j n-2; j 0; j--) { dp[m-1][j] Math.max(1, dp[m-1][j1] - dungeon[m-1][j]); } for(int i m-2; i 0; i--) { for(int j n-2; j 0; j--) { int min Math.min(dp[i1][j], dp[i][j1]); dp[i][j] Math.max(1, min - dungeon[i][j]); } } return dp[0][0]; }4. 动态规划解题方法论通过以上问题的分析我们可以总结出解决动态规划问题的一般步骤定义状态明确dp数组或dp表的含义确定状态表示什么状态转移方程找出状态之间的关系建立递推公式初始化确定初始条件处理边界情况确定计算顺序明确填表顺序保证计算当前状态时所需的前置状态已经计算空间优化考虑是否可以优化空间复杂度如使用滚动数组等技巧对于路径类问题还需要特别注意网格边界条件的处理移动方向的限制只能向右/向下或可以多方向移动是否需要考虑障碍物或特殊格子是求路径数量还是最优值最大/最小5. 常见错误与调试技巧在实现动态规划算法时常见的错误包括数组越界特别是在处理边界条件时解决方法仔细检查循环的起始和终止条件初始化错误初始条件设置不正确导致后续计算错误解决方法单独处理边界情况确保初始值正确状态转移方程错误未能正确表达状态之间的关系解决方法用简单例子手动验证状态转移方程空间复杂度优化导致的错误在优化空间时覆盖了还需要使用的值解决方法记录中间变量或改变计算顺序调试技巧打印dp表观察中间结果用小的测试用例手动计算与程序输出对比分步验证状态转移方程的正确性6. 动态规划的优化方向对于更复杂的动态规划问题可以考虑以下优化方向状态压缩当状态可以表示为位模式时使用位运算优化斜率优化对于特定形式的状态转移方程可以优化时间复杂度四边形不等式优化适用于区间DP问题单调队列优化优化滑动窗口类问题矩阵快速幂对于线性递推关系可以优化到对数时间复杂度7. 实际应用中的注意事项在实际工程中应用动态规划时还需要考虑大数处理使用long类型或取模运算防止溢出内存限制对于大规模问题可能需要优化空间或使用外部存储多线程优化某些DP问题可以并行计算预处理和后处理有时需要对输入数据进行预处理或对结果进行后处理动态规划是一种强大的算法设计技术掌握它需要大量的练习和经验积累。建议从简单问题开始逐步挑战更复杂的问题同时注意总结各类问题的共性和特性。
企业数字化 ERP 产品动态
相关推荐
GA-Elman模型在时序预测中的Matlab实现与优化 1. 时序预测与GA-Elman模型概述时序预测是数据分析领域的重要分支,广泛应用于电力负荷预测、股票价格分析、气象预报等场景。与传统静态数据不同,时序数据具有明显的时间依赖性,这就要求预测模型必须具备记忆历史信息的能力。在众多时序预测方… · 2026/9/23 17:48:09
巨量算数参数加密解析:X-Bogus、msToken与-signature全流程还原 简介:最新巨量算数(X-Bogus、-signature、msToken)参数加密分析结果聚焦巨量引擎接口的签名与令牌机制,面向爬虫开发、业务风控及安全研究人员,解决请求参数逆向与自动化生成问题,覆盖X-Bogus、_signature和… · 2026/9/23 17:48:02
阿里云ECS部署Oracle 19c RAC三大核心避坑指南 简介:本资源是一份面向Oracle DBA与云平台运维工程师的实战型部署手册,聚焦阿里云ECS环境下CentOS 7.6系统上Oracle 19c RAC双节点集群的全流程落地——从环境准备、存储与网络精细化规划,到安装、优化及日常维护。手册覆盖OCR/DATA/FRA三类A… · 2026/9/23 17:48:02
MODIS NDVI数据预处理全流程详解:从HDF到出图实用指南 简介:面向环境遥感、生态评估与地理信息分析人员,提供2015年中国区域1km分辨率NDVI栅格数据。原始数据源自NASA MOD13A3月合成产品,经提取子数据集、拼接、投影栅格、单位换算、边界裁剪等步骤,并采用最大合成法生成年度植被指数&… · 2026/9/23 18:23:19
遂宁二中实验学校开发避坑:新手3招搞定代码调试 遂宁二中实验学校开发避坑:新手3招搞定代码调试 刚拿到遂宁二中实验学校的开发任务书,是不是感觉脑子发懵?看着那些参数和接口文档,心里直打鼓:这玩意儿到底怎么跑起来?更头疼的是,从网上复制来的示例代码,粘贴到本地环境里,直接报错。红色的… · 2026/9/23 18:23:19
蓝牙协议栈开发权威资料包:Core Spec v5.4 官方文档全集 简介:本资源是一套面向嵌入式开发工程师、无线通信学习者及物联网技术从业者的蓝牙协议深度学习资料合集,聚焦Bluetooth核心规范与BLE低功耗实现原理,助力系统理解协议栈各层机制并支撑实际开发与调试。压缩包共106个文件,含56份权… · 2026/9/23 18:23:19
高校科研管理系统源码解析:Servlet+JSP全生命周期实战 简介:本资源是一套完整的高校科研管理信息系统毕业设计源码,面向计算机专业本科生及Java Web初学者,解决高校科研成果申报、审核与统计分析的实际业务需求。系统采用JSPServlet架构,支持管理员、学校领导、院系秘书、科研审核员和… · 2026/9/23 18:23:13
斗牛獒性能优化完整示例:3步解决项目卡顿 斗牛獒性能优化完整示例:3步解决项目卡顿 看了一堆教程还是不会写项目?别急,问题往往不在代码逻辑,而在底层性能。今天直接上 斗牛獒 这个典型场景的 完整示例 ,带你从瓶颈定位到优化落地,全程实战。 一、性能瓶颈:为什么你的项目慢得像牛拉磨… · 2026/9/23 18:23:07
AI教材编写实用方法汇总 适配高校多学科专业教材产出需求 写教材之前,选工具真是一件让人头疼的事!用常见的办公软件,功能太简单,框架设计和格式调整全得自己手动操作,特别麻烦;而用那些专门的教材编写软件,界面复杂,操作难,光是… · 2026/9/23 18:23:07
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29