动态规划入门:从斐波那契数列到算法优化

发布时间:2026/9/10 16:56:34
动态规划入门:从斐波那契数列到算法优化 1. 斐波那契数列与动态规划入门第一次接触动态规划时我盯着斐波那契数列的递归解法看了整整一个下午。那个看似优雅的数学公式在代码中变成了性能黑洞——计算fib(40)居然需要好几秒这让我意识到算法设计不能只追求代码简洁更要考虑实际执行效率。动态规划正是为解决这类重复计算问题而生的经典方法。斐波那契数列定义为F(0)0F(1)1F(n)F(n-1)F(n-2)n≥2。这个数列在自然界中随处可见比如向日葵的螺旋数、蜜蜂的繁殖规律。但在计算机领域它更重要的价值是作为理解动态规划的Hello World案例。提示动态规划的核心思想是将大问题分解为重叠子问题通过存储中间结果避免重复计算。这与分治算法不同——分治的子问题是独立的而动态规划的子问题是相互依赖的。2. 从递归到动态规划的演进路径2.1 朴素递归解法及其缺陷def fib(n): if n 1: return n return fib(n-1) fib(n-2)这个实现虽然直接对应数学定义但存在严重的性能问题。以计算fib(5)为例调用树中fib(2)被计算了3次fib(3)被计算了2次。时间复杂度达到O(2^n)空间复杂度O(n)。2.2 记忆化搜索优化memo {} def fib(n): if n in memo: return memo[n] if n 1: return n memo[n] fib(n-1) fib(n-2) return memo[n]通过引入字典存储已计算结果将时间复杂度降为O(n)空间复杂度仍为O(n)。这是典型的自顶向下动态规划实现。2.3 迭代式动态规划def fib(n): if n 0: return 0 dp [0] * (n1) dp[1] 1 for i in range(2, n1): dp[i] dp[i-1] dp[i-2] return dp[n]使用数组显式存储所有子问题解采用自底向上的填充方式。这种写法更符合动态规划的标准范式便于扩展到更复杂的问题。2.4 空间优化版本def fib(n): if n 0: return 0 prev, curr 0, 1 for _ in range(2, n1): prev, curr curr, prev curr return curr观察到当前状态只依赖前两个状态可将空间复杂度优化到O(1)。这种滚动数组技巧在动态规划中非常常见。3. 动态规划的核心要素解析3.1 重叠子问题性质斐波那契数列问题中计算fib(n)需要重复计算fib(n-1)、fib(n-2)等子问题。动态规划通过存储这些子问题的解避免了指数级重复计算。3.2 最优子结构性质大问题的最优解可以由小问题的最优解推导得出。在斐波那契中fib(n)直接等于fib(n-1)fib(n-2)完美符合这一特性。3.3 状态转移方程斐波那契数列的状态转移方程极其简单dp[n] dp[n-1] dp[n-2]这是动态规划问题的灵魂公式不同问题的难度往往体现在状态转移方程的复杂度上。3.4 边界条件处理必须明确定义初始状态dp[0] 0 dp[1] 1在实际工程中边界条件的正确处理常常是bug的高发区。4. 动态规划的通用解题框架4.1 问题拆解步骤定义状态明确dp数组的含义确定转移方程找出状态间的关系式设置初始条件确定计算的起点选择计算顺序自顶向下或自底向上空间优化考虑是否能用更少空间4.2 斐波那契问题的标准解法模板def fib(n): # 边界处理 if n 2: return n # 初始化状态 dp [0] * (n 1) dp[1] 1 # 状态转移 for i in range(2, n 1): dp[i] dp[i - 1] dp[i - 2] return dp[n]4.3 复杂度分析技巧时间复杂度循环次数 × 每次循环的计算量空间复杂度dp数组的规模在斐波那契问题中分别为O(n)和O(n)未优化版本5. 动态规划的常见变体与应用5.1 爬楼梯问题每次可以爬1或2个台阶到第n阶有多少种方法本质上就是斐波那契数列问题。这类问题的变形在面试中非常常见。5.2 打家劫舍问题def rob(nums): if not nums: return 0 n len(nums) if n 1: return nums[0] dp [0] * n dp[0] nums[0] dp[1] max(nums[0], nums[1]) for i in range(2, n): dp[i] max(dp[i-1], dp[i-2] nums[i]) return dp[-1]这个问题的状态转移方程与斐波那契类似但增加了选择条件。5.3 最小花费爬楼梯def minCostClimbingStairs(cost): n len(cost) dp [0] * (n 1) for i in range(2, n 1): dp[i] min(dp[i-1] cost[i-1], dp[i-2] cost[i-2]) return dp[n]展示了动态规划在优化问题中的应用状态转移时需要比较不同选择。6. 动态规划的调试与优化技巧6.1 打印DP表调试法def fib(n): dp [0] * (n 1) dp[1] 1 for i in range(2, n 1): dp[i] dp[i-1] dp[i-2] print(fdp[{i}] {dp[i]}) return dp[n]通过观察DP表的填充过程可以直观发现状态转移是否正确。6.2 记忆化搜索的缓存命中分析from functools import lru_cache lru_cache(maxsizeNone) def fib(n): if n 2: return n return fib(n-1) fib(n-2) print(fib.cache_info()) # 查看缓存命中情况Python的lru_cache装饰器可以自动实现记忆化并提供了缓存统计功能。6.3 空间优化的边界条件测试当实现空间优化版本时要特别注意n0和n1等边界情况def fib(n): if n 0: # 必须单独处理 return 0 prev, curr 0, 1 for _ in range(2, n 1): prev, curr curr, prev curr return curr7. 动态规划与其他算法的对比7.1 与分治算法的区别分治子问题独立无重叠如归并排序动态规划子问题重叠需要记忆化7.2 与贪心算法的关系贪心局部最优选择不能保证全局最优动态规划考虑所有可能性保证全局最优7.3 与回溯法的适用场景回溯需要所有解的情况动态规划只需要最优解或计数8. 动态规划在实际工程中的应用8.1 文本排版优化在LaTeX或Word的断字算法中动态规划用于最小化不整齐度def justify_text(words, maxWidth): n len(words) dp [float(inf)] * n dp[0] (maxWidth - len(words[0])) ** 2 for i in range(1, n): length len(words[i]) dp[i] dp[i-1] (maxWidth - length) ** 2 total length for j in range(i-1, -1, -1): total len(words[j]) 1 if total maxWidth: break cost (maxWidth - total) ** 2 if j 0: dp[i] min(dp[i], cost) else: dp[i] min(dp[i], dp[j-1] cost) return dp[-1]8.2 资源调度问题在云计算资源分配中动态规划可用于优化虚拟机部署def schedule_tasks(tasks, capacity): n len(tasks) dp [[0] * (capacity 1) for _ in range(n 1)] for i in range(1, n 1): weight, value tasks[i-1] for w in range(1, capacity 1): if weight w: dp[i][w] dp[i-1][w] else: dp[i][w] max(dp[i-1][w], dp[i-1][w-weight] value) return dp[n][capacity]9. 动态规划的学习路线建议9.1 基础阶段斐波那契数列系列问题爬楼梯及其变种背包问题01背包、完全背包矩阵路径问题9.2 进阶阶段序列型动态规划LIS、LCS区间型动态规划状态压缩动态规划树形动态规划9.3 经典题库推荐LeetCode动态规划专题《算法导论》动态规划章节《算法竞赛入门经典》相关章节10. 动态规划常见误区与避坑指南10.1 错误的状态定义新手常犯的错误是dp数组定义不准确。例如在斐波那契问题中有人会错误地定义dp[i]为前i项的和这会导致完全错误的状态转移。10.2 忽略边界条件没有正确处理n0或n1的情况是常见bug来源。在面试中面试官通常会特意测试这些边界条件。10.3 过早优化在初学阶段应该先写出清晰的标准动态规划实现再考虑空间优化。过早优化可能会引入难以发现的错误。10.4 混淆遍历顺序自顶向下记忆化搜索和自底向上迭代法各有适用场景。对于某些问题如依赖后效性问题只能使用特定顺序。11. 斐波那契数列的数学特性与优化11.1 矩阵快速幂解法利用矩阵乘法性质可以将时间复杂度优化到O(log n)def matrix_mult(a, b): return [ [a[0][0]*b[0][0] a[0][1]*b[1][0], a[0][0]*b[0][1] a[0][1]*b[1][1]], [a[1][0]*b[0][0] a[1][1]*b[1][0], a[1][0]*b[0][1] a[1][1]*b[1][1]] ] def matrix_pow(mat, power): result [[1,0],[0,1]] # 单位矩阵 while power 0: if power % 2 1: result matrix_mult(result, mat) mat matrix_mult(mat, mat) power // 2 return result def fib(n): if n 0: return 0 mat [[1,1],[1,0]] result matrix_pow(mat, n-1) return result[0][0]11.2 通项公式法斐波那契数列有精确的数学公式F(n) (φ^n - ψ^n)/√5 其中φ(1√5)/2≈1.618, ψ(1-√5)/2≈-0.618但由于浮点数精度问题这种方法在实际编程中并不常用。11.3 快速倍增法利用数学恒等式实现O(log n)时间复杂度def fib(n): def fast_doubling(m): if m 0: return (0, 1) a, b fast_doubling(m 1) c a * (2 * b - a) d a * a b * b if m 1: return (d, c d) else: return (c, d) return fast_doubling(n)[0]12. 动态规划在算法竞赛中的特殊技巧12.1 滚动数组优化对于多维DP问题可以通过模运算减少空间使用def dp_with_rolling_array(n): dp [[0]*2 for _ in range(2)] # 只保留必要的两行 dp[0][0] 1 # 初始状态 for i in range(1, n1): curr, prev i % 2, (i-1) % 2 dp[curr][0] dp[prev][0] dp[prev][1] dp[curr][1] dp[prev][0] return dp[n % 2][0] dp[n % 2][1]12.2 状态压缩技巧当状态可以用位表示时可以用整数代替数组def tsp(graph): n len(graph) dp [[float(inf)] * n for _ in range(1n)] dp[1][0] 0 # 从城市0出发 for mask in range(1, 1n): for u in range(n): if not (mask (1u)): continue for v in range(n): if mask (1v): continue new_mask mask | (1v) dp[new_mask][v] min(dp[new_mask][v], dp[mask][u] graph[u][v]) return min(dp[(1n)-1][u] graph[u][0] for u in range(1, n))13. 动态规划问题的识别特征13.1 典型问题特征问题可以分解为重叠子问题具有最优子结构性质子问题的解可以被缓存和重用通常涉及最优化或计数问题13.2 常见问题模式有多少种方式...计数问题最大/最小的...是什么最优化问题能否...可行性问题最长的...子序列序列问题13.3 不适合动态规划的场景子问题不重叠的情况问题不具备最优子结构需要输出所有具体解而非计数或最优值14. 动态规划在机器学习中的应用14.1 维特比算法在隐马尔可夫模型中用于找到最可能的状态序列def viterbi(obs, states, start_p, trans_p, emit_p): V [{}] for st in states: V[0][st] {prob: start_p[st] * emit_p[st][obs[0]], prev: None} for t in range(1, len(obs)): V.append({}) for st in states: max_tr_prob max(V[t-1][prev_st][prob]*trans_p[prev_st][st] for prev_st in states) for prev_st in states: if V[t-1][prev_st][prob] * trans_p[prev_st][st] max_tr_prob: max_prob max_tr_prob * emit_p[st][obs[t]] V[t][st] {prob: max_prob, prev: prev_st} break opt [] max_prob max(value[prob] for value in V[-1].values()) previous None for st, data in V[-1].items(): if data[prob] max_prob: opt.append(st) previous st break for t in range(len(V)-2, -1, -1): opt.insert(0, V[t1][previous][prev]) previous V[t1][previous][prev] return opt14.2 动态时间规整(DTW)用于时间序列对齐def dtw(s, t): n, m len(s), len(t) dp [[float(inf)] * (m1) for _ in range(n1)] dp[0][0] 0 for i in range(1, n1): for j in range(1, m1): cost abs(s[i-1] - t[j-1]) dp[i][j] cost min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) return dp[n][m]15. 动态规划在系统设计中的应用15.1 数据库查询优化在SQL查询计划优化中动态规划用于选择最优的连接顺序def optimal_join_order(tables, join_costs): n len(tables) dp [[(float(inf), None) for _ in range(n)] for _ in range(n)] for i in range(n): dp[i][i] (0, tables[i]) for length in range(2, n1): for i in range(n - length 1): j i length - 1 for k in range(i, j): cost dp[i][k][0] dp[k1][j][0] join_cost(dp[i][k][1], dp[k1][j][1]) if cost dp[i][j][0]: dp[i][j] (cost, join_tables(dp[i][k][1], dp[k1][j][1])) return dp[0][n-1]15.2 网络路由优化在计算机网络中动态规划用于计算最短路径def shortest_path(graph, start, end): n len(graph) dist [float(inf)] * n dist[start] 0 prev [None] * n for _ in range(n-1): for u in range(n): for v, weight in graph[u]: if dist[u] weight dist[v]: dist[v] dist[u] weight prev[v] u path [] u end while prev[u] is not None: path.insert(0, u) u prev[u] path.insert(0, u) return path, dist[end]16. 动态规划在游戏开发中的应用16.1 游戏AI决策在回合制策略游戏中动态规划可用于计算最优行动序列def game_ai_dp(game_state, depth): if depth 0 or game_state.is_terminal(): return evaluate(game_state) best_value -float(inf) for action in game_state.get_actions(): new_state game_state.apply_action(action) value -game_ai_dp(new_state, depth-1) # 对手会采取相反策略 if value best_value: best_value value return best_value16.2 装备合成系统在RPG游戏中优化装备升级路径def optimize_upgrade(items, target, recipes): dp {item: (float(inf), None) for item in items} dp[target] (0, None) updated True while updated: updated False for recipe in recipes: output, inputs, cost recipe total_cost sum(dp[i][0] for i in inputs) cost if total_cost dp[output][0]: dp[output] (total_cost, inputs) updated True return dp17. 动态规划在金融领域的应用17.1 期权定价Black-Scholes模型中的动态规划实现def option_pricing(S, K, T, r, sigma, N1000): dt T / N u math.exp(sigma * math.sqrt(dt)) d 1 / u p (math.exp(r * dt) - d) / (u - d) # 初始化期权价值树 V [[0] * (i1) for i in range(N1)] for j in range(N1): ST S * (u ** (N-j)) * (d ** j) V[N][j] max(ST - K, 0) # 反向递推 for i in range(N-1, -1, -1): for j in range(i1): V[i][j] math.exp(-r * dt) * (p * V[i1][j] (1-p) * V[i1][j1]) return V[0][0]17.2 投资组合优化Markowitz均值-方差模型def portfolio_optimization(returns, target_return): n len(returns) mean_returns np.mean(returns, axis0) cov_matrix np.cov(returns.T) # 使用动态规划处理约束 dp np.zeros((n1, target_return*1001)) dp[0, 0] 1 for i in range(1, n1): for r in range(target_return*1001): if dp[i-1, r]: for add_r in range(int(mean_returns[i-1]*100), target_return*1001): if r add_r target_return*100: dp[i, radd_r] 1 # 找到满足收益约束的最小方差组合 valid_returns np.where(dp[n] 1)[0] min_variance float(inf) best_weights None for r in valid_returns: weights ... # 根据r计算权重 variance weights.T cov_matrix weights if variance min_variance: min_variance variance best_weights weights return best_weights18. 动态规划在生物信息学中的应用18.1 DNA序列比对Needleman-Wunsch算法def dna_alignment(seq1, seq2, match1, mismatch-1, gap-1): m, n len(seq1), len(seq2) dp [[0]*(n1) for _ in range(m1)] # 初始化边界条件 for i in range(1, m1): dp[i][0] dp[i-1][0] gap for j in range(1, n1): dp[0][j] dp[0][j-1] gap # 填充DP表 for i in range(1, m1): for j in range(1, n1): if seq1[i-1] seq2[j-1]: diag dp[i-1][j-1] match else: diag dp[i-1][j-1] mismatch up dp[i-1][j] gap left dp[i][j-1] gap dp[i][j] max(diag, up, left) # 回溯找出最优比对 align1, align2 [], [] i, j m, n while i 0 or j 0: if i 0 and j 0 and dp[i][j] dp[i-1][j-1] (match if seq1[i-1] seq2[j-1] else mismatch): align1.append(seq1[i-1]) align2.append(seq2[j-1]) i - 1 j - 1 elif i 0 and dp[i][j] dp[i-1][j] gap: align1.append(seq1[i-1]) align2.append(-) i - 1 else: align1.append(-) align2.append(seq2[j-1]) j - 1 return .join(reversed(align1)), .join(reversed(align2)), dp[m][n]18.2 蛋白质折叠预测简化版的蛋白质折叠能量最小化def protein_folding(sequence): n len(sequence) # dp[i][j][k] 表示从i到j的子序列在k方向上的最小能量 dp [[[float(inf)]*4 for _ in range(n)] for _ in range(n)] # 初始化单个氨基酸 for i in range(n): for d in range(4): dp[i][i][d] 0 # 填充DP表 for length in range(2, n1): for i in range(n - length 1): j i length - 1 for d in range(4): min_energy float(inf) for k in range(i, j): for d1 in range(4): for d2 in range(4): energy dp[i][k][d1] dp[k1][j][d2] interaction_energy(sequence, i, j, k, d1, d2) if energy min_energy: min_energy energy dp[i][j][d] min_energy return min(min(dp[0][n-1]))19. 动态规划在图像处理中的应用19.1 图像拼接与接缝裁剪动态规划实现的内容感知缩放def seam_carving(energy_map): h, w energy_map.shape dp np.zeros((h, w)) dp[0] energy_map[0] for i in range(1, h): for j in range(w): if j 0: dp[i][j] energy_map[i][j] min(dp[i-1][j], dp[i-1][j1]) elif j w-1: dp[i][j] energy_map[i][j] min(dp[i-1][j-1], dp[i-1][j]) else: dp[i][j] energy_map[i][j] min(dp[i-1][j-1], dp[i-1][j], dp[i-1][j1]) # 回溯找到最小能量接缝 seam [] j np.argmin(dp[-1]) seam.append((h-1, j)) for i in range(h-2, -1, -1): if j 0: j j if dp[i][j] dp[i][j1] else j1 elif j w-1: j j-1 if dp[i][j-1] dp[i][j] else j else: min_idx j-1 np.argmin([dp[i][j-1], dp[i][j], dp[i][j1]]) j min_idx seam.append((i, j)) return seam19.2 图像配准基于动态规划的图像对齐算法def image_alignment(img1, img2, max_offset20): h, w img1.shape cost np.zeros((2*max_offset1, 2*max_offset1)) # 计算所有可能偏移的匹配代价 for dy in range(-max_offset, max_offset1): for dx in range(-max_offset, max_offset1): y1_start max(0, dy) y1_end min(h, h dy) x1_start max(0, dx) x1_end min(w, w dx) y2_start max(0, -dy) y2_end min(h, h - dy) x2_start max(0, -dx) x2_end min(w, w - dx) patch1 img1[y1_start:y1_end, x1_start:x1_end] patch2 img2[y2_start:y2_end, x2_start:x2_end] cost[dymax_offset, dxmax_offset] np.sum((patch1 - patch2)**2) # 动态规划寻找最优路径 dp np.zeros((2*max_offset1, 2*max_offset1)) path np.zeros((2*max_offset1, 2*max_offset1, 2), dtypeint) for dy in range(2*max_offset1): for dx in range(2*max_offset1): if dy 0 and dx 0: dp[dy, dx] cost[dy, dx] continue min_prev float(inf) prev_dy, prev_dx -1, -1 for ddy in [-1, 0, 1]: for ddx in [-1, 0, 1]: if ddy 0 and ddx 0: continue py, px dy ddy, dx ddx if 0 py 2*max_offset1 and 0 px 2*max_offset1: if dp[py, px] min_prev: min_prev dp[py, px] prev_dy, prev_dx py, px dp[dy, dx] min_prev cost[dy, dx] path[dy, dx] [prev_dy, prev_dx] # 回溯找到最优偏移序列 min_pos np.unravel_index(np.argmin(dp), dp.shape) offset_sequence [] current min_pos while True: offset_sequence.append((current[0]-max_offset, current[1]-max_offset)) if current (max_offset, max_offset): break current tuple(path[current]) return offset_sequence[::-1]20. 动态规划的未来发展趋势20.1 与深度学习的结合现代研究正在探索将动态规划与神经网络结合如使用神经网络学习状态转移方程用动态规划指导神经网络训练记忆网络与动态规划的结合20.2 自动动态规划生成新兴技术尝试自动识别问题中的最优子结构从问题描述自动推导状态定义机器学习辅助的状态转移方程生成动态规划模板的自动选择20.3 分布式动态规划处理超大规模问题的方向MapReduce实现的动态规划算法GPU加速的状态转移计算分层分解的动态规划框架20.4 近似动态规划针对NP难问题的实用解法状态空间抽样技术启发式剪枝策略近似误差控制方法在实际工程中我发现动态规划最难的不是写出状态转移方程而是如何准确定义状态。一个好的状态定义应该包含足够的信息来做出后续决策但又不能包含冗余信息导致状态爆炸。这需要大量的实践和经验积累。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询