算法面试题:数据结构、DFS/BFS、二分、DP 与滑动窗口
前端算法面试不一定追求竞赛难度,但会考察你拆问题、分析复杂度、写出稳定代码的能力。
数据结构
数组与链表
知识点讲解
数组支持随机访问,按索引读取是 O(1),但中间插入删除通常是 O(n)。链表插入删除节点方便,但查找需要遍历。面试中数组常配合双指针、滑动窗口、前缀和;链表常考反转、快慢指针、环检测、合并。
面试常问
问:判断链表是否有环怎么做?
答:用快慢指针。慢指针每次走一步,快指针每次走两步。如果有环,两者一定会相遇;如果快指针走到空,说明无环。
栈与队列
知识点讲解
栈是后进先出,适合括号匹配、单调栈、函数调用、撤销操作。队列是先进先出,适合 BFS、任务调度。双端队列常用于滑动窗口最大值。
面试常问
问:有效括号怎么做?
答:遇到左括号入栈,遇到右括号就看栈顶是否匹配。最后栈为空才合法。关键是提前处理栈空和类型不匹配。
树与图
知识点讲解
树题重点是递归定义:一棵树的问题通常可以拆成当前节点和左右子树。图题重点是建图、访问标记和遍历策略。无权最短路用 BFS,有权最短路才考虑 Dijkstra。
面试常问
问:二叉树最大深度怎么求?
答:递归返回左右子树最大深度加一;或者层序遍历,每遍历一层深度加一。
常见算法思想
DFS 与 BFS
知识点讲解
DFS 适合路径搜索、回溯、连通块;BFS 适合层级遍历和无权最短路径。DFS 通常用递归或栈,BFS 通常用队列。
面试常问
问:岛屿数量怎么做?
答:遍历网格,遇到陆地就计数,并用 DFS 或 BFS 把相连陆地全部标记为已访问。时间复杂度 O(mn)。
二分查找
知识点讲解
二分的本质是在单调性上排除一半答案。可以二分数组下标,也可以二分答案。关键是定义搜索区间、循环条件和边界收缩方式。
面试常问
问:二分为什么容易死循环?
答:边界更新没有保证区间变小,或者 mid 偏向错误。写题前先明确用闭区间 [left, right] 还是左闭右开 [left, right)。
滑动窗口
知识点讲解
滑动窗口适合连续子数组或子串问题。右指针扩张窗口,左指针在条件不满足或需要收缩时移动。核心是维护窗口内的状态。
面试常问
问:最长无重复子串怎么做?
答:用哈希表记录字符最近位置,右指针遍历字符,如果字符重复且在窗口内,就移动左指针到重复字符后一位,同时更新最大长度。
动态规划与贪心
动态规划
知识点讲解
DP 适合有重叠子问题和最优子结构的问题。步骤是定义状态、写转移方程、确定初始化、确定遍历顺序。前端面试常考爬楼梯、最长递增子序列、背包、路径问题。
面试常问
问:爬楼梯为什么是 DP?
答:到第 n 阶只能从 n-1 或 n-2 来,所以 dp[n] = dp[n-1] + dp[n-2]。它有重叠子问题,可以用滚动变量降到 O(1) 空间。
贪心
知识点讲解
贪心每一步选择局部最优,希望得到全局最优。它必须能证明局部选择不会破坏全局最优。常见题有区间调度、买卖股票、跳跃游戏。
面试常问
问:贪心和 DP 怎么区分?
答:如果每步局部最优能被证明导向全局最优,用贪心;如果当前选择依赖多个历史状态,通常用 DP。
复杂度表达
时间与空间复杂度
知识点讲解
面试里写完算法要主动说明复杂度。复杂度不是背答案,而是说明输入规模增长时运行时间和额外空间如何增长。
面试常问
问:递归空间复杂度怎么算?
答:除了显式数据结构,还要算调用栈。比如二叉树 DFS 最坏空间 O(n),平衡树平均 O(log n)。
底层追问与代码示例
二分查找模板
知识点讲解
二分题最专业的回答是先定义搜索区间和不变量。下面是闭区间模板:
1 | function lowerBound(nums, target) { |
面试常问
问:为什么不用 (left + right) / 2?
答:在 JS 里数组长度通常不会溢出到这个程度,但在 Java/C++ 中可能整数溢出。写 left + (right - left) / 2 是更通用的习惯。
LRU 缓存
知识点讲解
LRU 要求 get/put 都是 O(1),常用哈希表 + 双向链表。JS 的 Map 保持插入顺序,可以写一个简化版。
1 | class LRUCache { |
追问时可以说明:真实工程如果需要更高控制力,会用手写双向链表节点,避免依赖语言容器特性。
单调栈
知识点讲解
单调栈用来找下一个更大/更小元素。栈中维护一个单调序列,新元素进来时弹出不满足条件的元素。
1 | function dailyTemperatures(temperatures) { |
动态规划:最长递增子序列
知识点讲解
O(n²) DP 容易理解,O(n log n) 解法用贪心 + 二分维护每个长度的最小结尾。
1 | function lengthOfLIS(nums) { |
面试常问
问:tails 数组是真实 LIS 吗?
答:不一定。tails[i] 表示长度为 i + 1 的递增子序列的最小结尾,用来保证后续更容易接上新元素。