当前位置: 首页 > news >正文

动态规划面试真题解析

一、核心概念与高频考点

动态规划(DP)是面试中考察算法思维的核心题型,其核心思想是:

  1. 最优子结构:问题的最优解包含子问题的最优解。
  2. 重叠子问题:子问题被重复计算,需通过存储结果避免冗余。
  3. 状态转移方程:定义子问题间的关系,是解题关键。

高频问题类型

  • 序列型:如最长公共子序列(LCS)、最长上升子序列(LIS)。
  • 双序列型:如编辑距离、字符串比对。
  • 背包型:0-1背包、完全背包、多重背包。
  • 矩阵型:如机器人路径、数字三角形。
  • 区间型:如矩阵链乘、石子合并。

二、经典真题解析

1. 0-1背包问题

题目:给定物品重量 w[]、价值 v[] 和背包容量 W,求最大价值。

解析

  • 状态定义dp[i][j] 表示前 i 个物品在容量 j 时的最大价值。
  • 状态转移
    • 不选第 i 个物品:dp[i][j] = dp[i-1][j]
    • 选第 i 个物品:dp[i][j] = dp[i-1][j-w[i]] + v[i](需满足 j ≥ w[i])。
    • 综合:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])
  • 初始化dp[0][j] = 0(无物品时价值为0)。
  • 空间优化:使用一维数组 dp[j],逆序更新以避免覆盖。

代码示例

def knapsack_01(w, v, W):n = len(w)dp = [0] * (W + 1)for i in range(n):for j in range(W, w[i] - 1, -1):dp[j] = max(dp[j], dp[j - w[i]] + v[i])return dp[W]

2. 最长公共子序列(LCS)

题目:给定字符串 s1 和 s2,求最长公共子序列长度。

解析

  • 状态定义dp[i][j] 表示 s1[0..i-1] 和 s2[0..j-1] 的LCS长度。
  • 状态转移
    • 若 s1[i-1] == s2[j-1]dp[i][j] = dp[i-1][j-1] + 1
    • 否则:dp[i][j] = max(dp[i-1][j], dp[i][j-1])
  • 初始化dp[0][j] = dp[i][0] = 0(空字符串的LCS为0)。

代码示例

def lcs(s1, s2):m, n = len(s1), len(s2)dp = [[0] * (n + 1) for _ in range(m + 1)]for i in range(1, m + 1):for j in range(1, n + 1):if s1[i-1] == s2[j-1]:dp[i][j] = dp[i-1][j-1] + 1else:dp[i][j] = max(dp[i-1][j], dp[i][j-1])return dp[m][n]

3. 机器人路径问题(含障碍物)

题目:机器人从网格左上角到右下角,每次只能向右或向下移动,求路径数(含障碍物)。

解析

  • 状态定义dp[i][j] 表示到达 (i,j) 的路径数。
  • 状态转移
    • 若 (i,j) 无障碍物:dp[i][j] = dp[i-1][j] + dp[i][j-1]
    • 否则:dp[i][j] = 0
  • 初始化
    • 第一行和第一列:若遇到障碍物,后续路径数为0。
    • dp[0][0] = 1(起点无障碍物时)。

代码示例

def unique_paths_with_obstacles(obstacle_grid):m, n = len(obstacle_grid), len(obstacle_grid[0])dp = [[0] * n for _ in range(m)]dp[0][0] = 1 if obstacle_grid[0][0] == 0 else 0# 初始化第一行for j in range(1, n):if obstacle_grid[0][j] == 0 and dp[0][j-1] == 1:dp[0][j] = 1# 初始化第一列for i in range(1, m):if obstacle[i][0] == 0 and dp[i-1][0] == 1:dp[i][0] = 1# 填充DP表for i in range(1, m):for j in range(1, n):if obstacle_grid[i][j] == 0:dp[i][j] = dp[i-1][j] + dp[i][j-1]return dp[m-1][n-1]

三、解题框架与优化技巧

  1. 通用解题框架
    • 分解问题:定义子问题(如 dp[i][j])。
    • 找出递推关系:明确状态转移方程。
    • 初始化边界条件:处理基础情况(如空字符串、容量为0)。
    • 计算顺序:自底向上(迭代)或自顶向下(记忆化递归)。
  2. 优化技巧
    • 空间优化:滚动数组(如背包问题中降维)。
    • 状态压缩:用位运算或布尔数组减少空间。
    • 决策单调性:优化时间复杂度(如四边形不等式)。

四、面试应对策略

  1. 判断是否为DP问题
    • 问题是否求最大值/最小值、可行性或方案数?
    • 是否存在重叠子问题和最优子结构?
  2. 沟通思路
    • 先定义状态,再推导状态转移方程。
    • 明确初始条件和计算顺序。
    • 举例验证小规模输入(如 n=3 时的结果)。
  3. 避免常见错误
    • 忽略边界条件(如数组越界)。
    • 状态定义模糊(如未明确 i 和 j 的含义)。
    • 更新顺序错误(如背包问题中正序更新导致重复计算)。

以下是动态规划的典型例题及其完整代码实现,涵盖斐波那契数列、背包问题、最长公共子序列、最小路径和、机器人路径计数等经典问题:

1. 斐波那契数列(Fibonacci Sequence)

题目:给定整数 n,返回斐波那契数列的第 n 项(F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2))。
动态规划思路:用数组 dp 存储中间结果,避免递归重复计算。
代码

def fibonacci(n):if n <= 1:return ndp = [0] * (n + 1)dp[1] = 1for i in range(2, n + 1):dp[i] = dp[i - 1] + dp[i - 2]return dp[n]print(fibonacci(10))  # 输出: 55

2. 0-1背包问题

题目:给定物品重量 weights、价值 values 和背包容量 W,求最大价值。
动态规划思路dp[i][j] 表示前 i 个物品在容量 j 时的最大价值,状态转移方程为:

  • 不选第 i 个物品:dp[i][j] = dp[i-1][j]
  • 选第 i 个物品:dp[i][j] = dp[i-1][j-weights[i-1]] + values[i-1]
    代码(空间优化版)
def knapsack_01(weights, values, W):n = len(weights)dp = [0] * (W + 1)for i in range(n):for j in range(W, weights[i] - 1, -1):dp[j] = max(dp[j], dp[j - weights[i]] + values[i])return dp[W]weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
W = 8
print(knapsack_01(weights, values, W))  # 输出: 10

3. 最长公共子序列(LCS)

题目:给定字符串 str1 和 str2,求最长公共子序列的长度。
动态规划思路dp[i][j] 表示 str1 前 i 个字符和 str2 前 j 个字符的 LCS 长度。
代码

def longest_common_subsequence(str1, str2):m, n = len(str1), len(str2)dp = [[0] * (n + 1) for _ in range(m + 1)]for i in range(1, m + 1):for j in range(1, n + 1):if str1[i - 1] == str2[j - 1]:dp[i][j] = dp[i - 1][j - 1] + 1else:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])return dp[m][n]print(longest_common_subsequence("ABCDGH", "AEDFHR"))  # 输出: 3

4. 最小路径和

题目:给定 m x n 网格,每个格子有非负整数,从左上角到右下角的最小路径和(只能向右或向下移动)。
动态规划思路dp[i][j] 表示从起点到 (i,j) 的最小路径和。
代码

def min_path_sum(grid):m, n = len(grid), len(grid[0])dp = [[0] * n for _ in range(m)]dp[0][0] = grid[0][0]for i in range(1, m):dp[i][0] = dp[i - 1][0] + grid[i][0]for j in range(1, n):dp[0][j] = dp[0][j - 1] + grid[0][j]for i in range(1, m):for j in range(1, n):dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]return dp[m - 1][n - 1]grid = [[1, 3, 1],[1, 5, 1],[4, 2, 1]
]
print(min_path_sum(grid))  # 输出: 7

5. 机器人路径计数(无障碍物)

题目:机器人从 m x n 网格的左上角出发,每次只能向右或向下移动,求到右下角的路径数。
动态规划思路dp[i][j] 表示到 (i,j) 的路径数,状态转移方程为 dp[i][j] = dp[i-1][j] + dp[i][j-1]
代码(空间优化版)

def unique_paths(m, n):dp = [1] * nfor _ in range(1, m):for j in range(1, n):dp[j] += dp[j - 1]return dp[-1]print(unique_paths(3, 7))  # 输出: 28

6. 机器人路径计数(有障碍物)

题目:在 m x n 网格中,障碍物用 1 表示,求机器人到右下角的路径数。
动态规划思路:若 (i,j) 是障碍物,则 dp[i][j] = 0;否则按无障碍物情况计算。
代码

def unique_paths_with_obstacles(obstacle_grid):m, n = len(obstacle_grid), len(obstacle_grid[0])dp = [[0] * n for _ in range(m)]dp[0][0] = 1 if obstacle_grid[0][0] == 0 else 0for i in range(1, m):if obstacle_grid[i][0] == 0 and dp[i - 1][0] == 1:dp[i][0] = 1for j in range(1, n):if obstacle_grid[0][j] == 0 and dp[0][j - 1] == 1:dp[0][j] = 1for i in range(1, m):for j in range(1, n):if obstacle_grid[i][j] == 0:dp[i][j] = dp[i - 1][j] + dp[i][j - 1]return dp[m - 1][n - 1]obstacle_grid = [[0, 0, 0],[0, 1, 0],[0, 0, 0]
]
print(unique_paths_with_obstacles(obstacle_grid))  # 输出: 2

动态规划是一种通过将复杂问题分解为相互重叠的子问题,并存储子问题的解以避免重复计算,从而高效解决最优化问题的算法思想。其核心在于定义状态(如 dp[i][j] 表示前 i 个元素在条件 j 下的最优解)、确定状态转移方程(如递推关系 dp[i] = max(dp[i-1], dp[i-2]+val)),并通过初始化边界条件和迭代填充状态表来逐步推导出最终解。典型应用场景包括斐波那契数列、背包问题、最长公共子序列、网格路径计数等,其优势在于将指数级时间复杂度优化为多项式级(如从 O(2ⁿ) 降至 O(n) 或 O(n²)),但需注意空间优化(如滚动数组)以减少内存消耗。

http://www.dtcms.com/a/341482.html

相关文章:

  • Linux网络服务(三)——DNS域名解析服务
  • 学习中需不需要划线、做笔记
  • 2-1.利用框架构建一个easy的web应用
  • CISP-PTE之路--09文
  • 拓扑排序判断环 P1347 排序题解
  • LeetCode 刷题【47. 全排列 II】
  • k8s笔记01
  • WIFI国家码修改信道方法_高通平台
  • 如何将数据从 iPhone 转移到 vivo?
  • 基于Python的反诈知识科普平台 Python+Django+Vue.js
  • 道路车道线分割数据集左车道右车道中线labelme格式3494张4类别
  • 工业电脑选得好生产效率节节高稳定可靠之选
  • Pycharm-002 Pycharm 编译器运行器不显示,日志不打印
  • MySQL 事务(重点)
  • GThinker多模态大模型:线索引导式反思的突破
  • Oracle官方文档翻译《Database Concepts 23ai》第2章-容器数据库与可插入数据库
  • Qwen Image edit的ComfyUI工作流搭建
  • vue中动态设置class类名和style样式
  • Javascript面试题及详细答案150道之(121-135)
  • 医学影像分析中的持续学习:近期进展与未来展望综述|文献速递-深度学习人工智能医疗图像
  • 42-Python基础语法-2
  • Lecture 5 GPUs课程笔记
  • Leetcode 深度优先搜索 (9)
  • Kafka-Kraft
  • leetcode3 无重复字符的最长子串
  • vue2封装日期选择组件--有时间选择版本
  • 创建Vue项目的不同方式及项目规范化配置
  • Playwright 与 Scrapy 的实际应用场景能力分析
  • 深入理解 Spring 的 @ControllerAdvice
  • 【AI应用】修改向量数据库Milvus默认密码