news 2026/8/26 2:42:03

动态规划与图论:得物校招笔试算法题解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
动态规划与图论:得物校招笔试算法题解析

1. 笔试题目解析与解题思路

得物2026年春季校招笔试第二套题目主要考察应聘者的算法设计能力和编程基本功。这套题目包含3道编程题,难度梯度合理,覆盖了字符串处理、动态规划和图论等常见考点。作为参加过多次技术笔试的面试官,我将从题目分析、解题思路和代码实现三个维度进行详细解读。

1.1 第一题:字符串模式匹配

题目要求实现一个支持通配符的字符串匹配功能。其中'?'可以匹配任意单个字符,'*'可以匹配任意长度字符串(包括空串)。这与LeetCode第44题高度相似,属于经典的动态规划问题。

核心解题思路是构建一个二维DP数组,其中dp[i][j]表示模式串前i个字符是否能匹配文本串前j个字符。状态转移方程需要考虑三种情况:

  1. 当p[i-1] == s[j-1]或p[i-1] == '?'时,dp[i][j] = dp[i-1][j-1]
  2. 当p[i-1] == '*'时,dp[i][j] = dp[i-1][j] || dp[i][j-1]
  3. 其他情况为false

边界条件处理:

  • 空模式只能匹配空字符串
  • 连续的'*'可以合并处理
def isMatch(s: str, p: str) -> bool: m, n = len(s), len(p) dp = [[False]*(m+1) for _ in range(n+1)] dp[0][0] = True for i in range(1, n+1): if p[i-1] == '*': dp[i][0] = dp[i-1][0] for i in range(1, n+1): for j in range(1, m+1): if p[i-1] == s[j-1] or p[i-1] == '?': dp[i][j] = dp[i-1][j-1] elif p[i-1] == '*': dp[i][j] = dp[i-1][j] or dp[i][j-1] return dp[n][m]

注意:实际笔试中需要处理大量边界case,如空字符串、全*模式等。建议先写出转移方程再编码。

1.2 第二题:二叉树路径求和

题目给定一棵二叉树和一个目标值,要求找出所有从根节点到叶子节点的路径,使得路径上节点值之和等于目标值。这是LeetCode第113题的变种,考察树的深度优先遍历。

解题关键步骤:

  1. 使用DFS遍历所有根到叶子的路径
  2. 维护当前路径和路径和
  3. 当到达叶子节点时检查sum是否等于target
  4. 注意结果需要深拷贝当前路径
class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def pathSum(root: TreeNode, target: int) -> List[List[int]]: res = [] def dfs(node, path, curr_sum): if not node: return curr_sum += node.val path.append(node.val) if not node.left and not node.right and curr_sum == target: res.append(list(path)) dfs(node.left, path, curr_sum) dfs(node.right, path, curr_sum) path.pop() dfs(root, [], 0) return res

优化点:

  • 提前终止:当curr_sum > target时可提前返回(适用于节点值均为正数的情况)
  • 路径记录:使用list会频繁拷贝,可改用双端队列提高性能

1.3 第三题:图的最短路径

题目给出一个带权有向图,要求计算从起点到终点的最短路径,且路径必须经过指定的中间节点。这是Dijkstra算法的进阶应用,考察图论知识的灵活运用。

分阶段解决方案:

  1. 计算起点到所有中间节点的最短路径
  2. 计算各中间节点到终点的最短路径
  3. 组合各段路径求最小值
import heapq def shortestPath(graph, start, end, intermediates): # 构建邻接表 adj = defaultdict(list) for u, v, w in graph: adj[u].append((v, w)) def dijkstra(src): dist = {node: float('inf') for node in adj} dist[src] = 0 heap = [(0, src)] while heap: d, u = heapq.heappop(heap) if d > dist[u]: continue for v, w in adj[u]: if dist[v] > dist[u] + w: dist[v] = dist[u] + w heapq.heappush(heap, (dist[v], v)) return dist # 阶段1:起点到所有中间点 start_dist = dijkstra(start) # 阶段2:各中间点到终点 end_dist = {} for mid in intermediates: end_dist[mid] = dijkstra(mid) # 组合结果 min_path = float('inf') for mid in intermediates: if start_dist[mid] != float('inf') and end_dist[mid][end] != float('inf'): min_path = min(min_path, start_dist[mid] + end_dist[mid][end]) return min_path if min_path != float('inf') else -1

实际笔试时要注意:

  1. 处理节点不可达的情况
  2. 考虑中间点顺序是否重要
  3. 大型图需要优化存储(稀疏图用邻接表)

2. 笔试技巧与时间管理

2.1 题目难度评估策略

在有限时间内(通常2-3小时),建议采用以下策略:

  1. 快速浏览所有题目,标注预期耗时
  2. 先完成最有把握的题目
  3. 中等难度题目争取部分分数
  4. 难题放在最后,至少写出思路

以本次笔试为例:

  • 字符串匹配:中等(20分钟)
  • 二叉树路径:简单(15分钟)
  • 图的最短路径:困难(35分钟)

2.2 代码编写规范

笔试评分会考察:

  1. 变量命名合理性
  2. 边界条件处理
  3. 代码可读性
  4. 注释说明关键步骤

建议模板:

# 函数功能说明 # @param 参数说明 # @return 返回值说明 def func(): # 步骤1注释 ... # 步骤2注释 ...

2.3 测试用例设计

必须自测的case类型:

  1. 空输入
  2. 极端值(如超大输入)
  3. 常规功能验证
  4. 特殊场景(如全相同字符)

例如字符串匹配题:

assert isMatch("", "") == True assert isMatch("aa", "*") == True assert isMatch("cb", "?a") == False assert isMatch("adceb", "*a*b") == True

3. 核心算法深度解析

3.1 动态规划优化技巧

对于字符串匹配问题,空间复杂度可优化为O(n):

def isMatch(s: str, p: str) -> bool: m, n = len(s), len(p) dp = [False]*(m+1) dp[0] = True for i in range(1, n+1): new_dp = [False]*(m+1) if p[i-1] == '*': new_dp[0] = dp[0] for j in range(1, m+1): if p[i-1] == s[j-1] or p[i-1] == '?': new_dp[j] = dp[j-1] elif p[i-1] == '*': new_dp[j] = dp[j] or new_dp[j-1] dp = new_dp return dp[m]

3.2 二叉树遍历的迭代实现

笔试中递归可能栈溢出,建议掌握迭代写法:

def pathSum(root: TreeNode, target: int) -> List[List[int]]: if not root: return [] res = [] stack = [(root, [root.val], root.val)] while stack: node, path, curr_sum = stack.pop() if not node.left and not node.right and curr_sum == target: res.append(path) if node.right: stack.append((node.right, path+[node.right.val], curr_sum+node.right.val)) if node.left: stack.append((node.left, path+[node.left.val], curr_sum+node.left.val)) return res

3.3 Dijkstra算法的正确性证明

为什么Dijkstra算法不能处理负权边?

  1. 贪心选择性质依赖非负权假设
  2. 负权边可能导致已确定最短路径的节点需要更新
  3. 示例:A->B(1), A->C(3), B->C(-2)
    • 按Dijkstra会先确定B的最短路径为1
    • 但实际上通过B到C的路径更短(1-2=-1)

替代方案:

  • Bellman-Ford算法:O(VE)时间复杂度,可处理负权
  • SPFA算法:队列优化的Bellman-Ford

4. 常见错误与调试技巧

4.1 字符串匹配易错点

  1. 模式串开头的多个'*'处理不当
    • 错误示例:isMatch("abc", "**a")应返回True
  2. 忘记初始化dp[0][0] = True
  3. 二维数组行列定义混淆(m vs n)

调试建议:

  • 打印DP表格可视化匹配过程
  • 对小样例手动计算验证

4.2 二叉树遍历陷阱

  1. 路径记录未深拷贝:
    # 错误写法 res.append(path) # 后续修改会影响已存储结果 # 正确写法 res.append(list(path))
  2. 节点值可能为负数,不能提前剪枝
  3. 空树未特殊处理

4.3 图算法注意事项

  1. 优先队列未处理重复节点:
    # 必须跳过已确定最短路径的节点 if d > dist[u]: continue
  2. 邻接表构建错误(单向/双向边)
  3. 未处理不可达情况(返回-1或特殊值)

调试方法:

  • 打印各点最短距离表
  • 可视化小规模图的执行过程

5. 进阶学习建议

5.1 字符串匹配算法扩展

  1. KMP算法:O(n)时间复杂度
    • 核心思想:部分匹配表(PMT)
    • 应用场景:无通配符的精确匹配
  2. 正则表达式引擎实现
    • Thompson NFA构造法
    • 回溯和记忆化优化

5.2 树形问题变种

  1. 路径总和III(任意节点起止)
    • 前缀和+哈希表解法
  2. 序列化和反序列化二叉树
    • 前序遍历+特殊分隔符
  3. 最近公共祖先(LCA)
    • 递归分治解法

5.3 图论专题突破

  1. Floyd-Warshall算法
    • 全源最短路径
    • 动态规划三循环实现
  2. A*搜索算法
    • 启发式函数设计
    • 游戏寻路应用
  3. 网络流算法
    • Ford-Fulkerson方法
    • 最大流最小割定理

6. 面试准备策略

6.1 刷题路线图

  1. 基础阶段(2周):
    • 数组/字符串操作
    • 基本数据结构实现
  2. 进阶阶段(3周):
    • 动态规划经典模型
    • 图论基础算法
  3. 冲刺阶段(1周):
    • 公司真题训练
    • 模拟面试演练

6.2 白板编程训练

  1. 规范书写:
    • 预留函数签名空间
    • 分步骤注释
  2. 边写边讲:
    • 明确变量含义
    • 解释算法选择理由
  3. 测试用例:
    • 主动提出验证方案
    • 讨论边界情况

6.3 系统设计基础

虽然笔试侧重算法,但面试可能涉及:

  1. 设计模式应用
    • 观察者模式
    • 工厂方法模式
  2. 分布式概念
    • CAP理论
    • 一致性哈希
  3. 数据库知识
    • 索引原理
    • 事务隔离级别
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/26 2:42:00

AI Agent工具选择指南:Codex、Claude Code、Trae、Zcode、Workbuddy对比

国内小白的第一款 AI Agent 工具怎么选?Codex、Claude Code、Workbuddy、Trae、Zcode 优缺点与上手门槛全对比这次我们直接聊一个很实际的问题:国内开发者想上手 AI Agent 编程工具,第一款到底选哪个?当前市面上被讨论最多的五款工…

作者头像 李华
网站建设 2026/8/26 2:41:32

Java后端开发:应届生职业成长与技术路线指南

1. Java后端开发:应届生的黄金赛道选择刚走出校园的计算机相关专业学生,面对五花八门的技术方向常常陷入选择困难。作为从业十年的老码农,我强烈建议将Java后端作为职业起点——这不是盲目跟风,而是基于技术生态、就业市场和成长曲…

作者头像 李华
网站建设 2026/8/26 2:39:50

软件测试面试全攻略:技巧与实战解析

1. 软件测试面试全攻略:从入门到精通作为一名在测试行业摸爬滚打多年的老兵,我深知面试对测试工程师的重要性。每次面试不仅是展示自己能力的机会,更是与同行交流学习的契机。今天我就把自己这些年积累的面试经验,以及带团队时总结…

作者头像 李华
网站建设 2026/8/26 2:33:37

告别上下文浪费:极简AI编码代理的终端优先之道

每次 AI 编码助手用到后半程,我心里都会冒出一阵熟悉的不安:它开始反复读同一个文件,回答速度肉眼可见地变慢,更气人的是,它还会把上一轮已经纠正过的错误再次犯一遍。把会话记录翻出来看,原因从来都不神秘…

作者头像 李华
网站建设 2026/8/26 2:32:36

两数之和算法解析与面试实战技巧

1. 题目背景与核心价值两数之和(Two Sum)作为LeetCode题库中的第一道题目,长期占据热题排行榜前列。这道题看似简单,却包含了算法设计中最基础的暴力枚举、哈希映射等核心思想。根据平台统计数据显示,超过80%的面试中都…

作者头像 李华
网站建设 2026/8/26 2:32:12

GLM-5.2 NVFP4后训练实战:从PTQ到部署全流程解析

把 GLM-5.2 的 NVFP4 后训练跑通,听起来只是一次量化转换,实际上涉及模型加载、校准数据、量化参数、导出格式、推理引擎和验证指标一整条链路。实际项目里最典型的卡点是“离线量化成功,但端到端推理失败”,原因不是单一环节写错…

作者头像 李华