ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

算法面试高频题型全解析:双指针、滑动窗口、动态规划实战技巧

算法面试高频题型全解析:双指针、滑动窗口、动态规划实战技巧 1. 算法面试到底在考什么先搞明白游戏规则很多人刷了几百道题LeetCode周赛也能稳定三题结果一到面试现场还是头皮发麻。我觉得问题通常不在题量而在于没搞明白算法面试本质上不是考试是一场“有限时间内的思维展示”。面试官不是想看你能不能AC而是想看你在面对一个陌生问题时怎么拆解、怎么选型、怎么在卡住时自救。1.1 面试官想要的东西和你想的不一样我做过几次面试官也陪朋友做过不少模拟面试。一个很直观的感受是能写出最优解的人很多但能把思路讲清楚、能把边界条件想全、能在提示下一步一步推进的人真的不多。面试官手里通常有一张评分表核心维度无非是四个正确性代码能不能跑通边界情况考虑是否周全复杂度时间复杂度和空间复杂度是否达标有没有明显可优化空间沟通能力能否边写边讲思路而不是闷头敲代码应变能力被提示或质疑时能否快速调整方案。这里有个很多候选人容易忽略的点算法题目的复杂度分析往往比代码本身更值钱。我以前遇到一个候选人一道“合并区间”的题代码写得很快也很干净但问到他时间复杂度是多少时他想了半天说“应该是O(nlogn)吧”。这种不确定性在面试官眼里是很减分的。算法题面试本质上是“思维过程外显化”你不仅要会做还要会讲你做出来的过程。1.2 刷题战略按高频题型分类而不是按难度刷“面试常考算法题”这个系列我在第一篇提过一个观点刷题要按题型分类不要按难度从Easy到Hard一路怼。面试考察的算法类型其实是高度集中的双指针、滑动窗口、动态规划、二叉树遍历、堆与Top K、图的最短路径这些才是真正的高频考点。为什么这类题型反复出现因为它们能高效地考察几项核心能力对数据结构特性的理解、对暴力解法到最优解法的优化能力、以及对边界条件的敏感度。比如“两数之和”可以从哈希表聊到双指针再延展到“三数之和”“四数之和”一道题能串起一串知识点面试官自然爱用。我自己刷题的习惯是每个高频题型找5-8道代表题反复做三遍。第一遍死磕第二遍限时训练第三遍不看答案直接手写。这个过程听起来简单但坚持下来你会发现面试时遇到新题脑子里的“题型匹配”速度会快很多。2. 五大高频题型实战拆解每道题都是套路这一篇我挑了五个面试里出现频率最高的题型每个题型配一道经典题从暴力解法一步步推导到最优解顺带把面试官常追问的点也标出来。用Python写因为Python的语法表达力强适合快速展示算法思维而且现在很多公司的面试都接受Python。2.1 双指针从“两数之和”到“三数之和”双指针是面试里最基础也最实用的技巧之一适用于有序数组或者可以排序的数组。核心思路是通过两个指针的相向移动把两层循环O(n²)的暴力枚举优化到O(n)。先看经典的“三数之和”题目给定一个数组nums找出所有和为0且不重复的三元组。暴力解法就是三层循环枚举所有组合时间复杂度O(n³)而且去重特别麻烦。用双指针的话流程是这样先对数组排序固定第一个数剩下两个数用双指针在右侧区间内寻找当前和小于0左指针右移大于0右指针左移相等时记录结果同时跳过重复值。def threeSum(nums): nums.sort() res [] n len(nums) for i in range(n - 2): # 跳过重复的第一个数 if i 0 and nums[i] nums[i - 1]: continue # 剪枝最小的数都大于0后面不可能和为0 if nums[i] 0: break left, right i 1, n - 1 while left right: total nums[i] nums[left] nums[right] if total 0: res.append([nums[i], nums[left], nums[right]]) # 跳过重复的左右指针值 while left right and nums[left] nums[left 1]: left 1 while left right and nums[right] nums[right - 1]: right - 1 left 1 right - 1 elif total 0: left 1 else: right - 1 return res这里有两个细节值得留意。第一是排序后剪枝nums[i] 0直接跳出循环因为后面的数都比它大三数之和不可能为0第二是去重逻辑必须在找到一个答案后把相同值的指针全部跳过否则会产生重复三元组。双指针的面试追问点通常有两个一是“如果数组里有重复元素怎么办”二是“如果要求返回下标而不是值怎么办”。第一个问题的答案是排序后跳过重复值第二个问题就不能用双指针了因为排序会打乱下标得用哈希表。2.2 滑动窗口连续子数组问题的万能钥匙滑动窗口适合处理“连续子序列/子数组”一类问题核心是维护一个窗口通过移动左右边界来穷尽所有合法的连续区间时间复杂度可以压到O(n)。典型题目是“无重复字符的最长子串”给定一个字符串找出其中不含有重复字符的最长子串长度。暴力做法是枚举所有子串然后判断是否含重复字符复杂度O(n²)。滑动窗口的做法是用两个指针left和right表示窗口的左右边界右指针不断向右扩展把新字符加入窗口一旦出现重复字符左指针移动到重复位置的下一个位置过程中不断更新窗口最大长度。def lengthOfLongestSubstring(s): window set() left 0 max_len 0 for right, char in enumerate(s): # 出现重复字符时收缩左边界 while char in window: window.remove(s[left]) left 1 window.add(char) max_len max(max_len, right - left 1) return max_len用集合来判重配合左指针的移动完美覆盖了“连续”和“无重复”这两个条件。滑动窗口的变体题非常多“最小覆盖子串”“长度最小的子数组”“字符串排列”都是同一个模板。我自己的经验是滑动窗口题的关键是搞清楚窗口的扩大条件、收缩条件、以及结果更新时机把这三个问题想清楚代码基本就在脑子里了。2.3 动态规划从暴力递归到状态转移方程动态规划是面试里的重头戏也是很多人的心理阴影。我的理解是动态规划其实就是“聪明的暴力搜索”——把中间结果存下来避免重复计算。要求解一个DP问题核心就四步定义状态、找出状态转移方程、初始化、确定遍历顺序。以经典题目“爬楼梯”为例一次可以爬1或2个台阶爬到第n阶有多少种不同的方法。如果你倒过来想到达第n阶的前一步只有两种可能——从第n-1阶跨1步或者从第n-2阶跨2步。所以dp[n] dp[n-1] dp[n-2]这就是状态转移方程。def climbStairs(n): if n 2: return n prev2, prev1 1, 2 # dp[1], dp[2] for i in range(3, n 1): cur prev1 prev2 prev2 prev1 prev1 cur return prev1这里用滚动变量的方式把空间复杂度从O(n)压到了O(1)这也是面试官喜欢追问的点“能不能优化空间复杂度”DP题的难点在于状态定义和转移方程的设计怎么找规律我的经验是先从暴力递归开始写然后看递归过程中哪些子问题被重复计算了把那些子问题用数组或哈希表存起来就是记忆化搜索再把递归改成迭代就是动态规划。这个路径对很多题都适用比如“打家劫舍”“编辑距离”“最长递增子序列”。2.4 二叉树递归思维的分水岭二叉树相关的题是面试的高频区因为它的天然递归结构能考察候选人的递归功底和对指针/引用的理解。经典题目“二叉树的最大深度”给定一个二叉树返回它的最大深度。递归解法非常直观一棵树的最大深度 max(左子树的最大深度, 右子树的最大深度) 1。def maxDepth(root): if not root: return 0 left_depth maxDepth(root.left) right_depth maxDepth(root.right) return max(left_depth, right_depth) 1这个代码这么短但面试官非常爱问“这个递归的空间复杂度是多少”答案是O(height)最坏情况下树退化成链表时是O(n)。另外还会追问“能不能不用递归实现”那就需要用栈进行迭代遍历本质上是自己模拟系统栈的行为。二叉树题目还有一套很常用的思维框架先确定遍历顺序前序、中序、后序、层序再确定在遍历过程中要做什么操作。比如“验证二叉搜索树”用中序遍历看是否递增“层序遍历”用队列实现BFS“最近公共祖先”用后序遍历做回溯。这套框架建立起来后大部分二叉树题都是一种套路。2.5 堆与Top K海量数据场景的高频考点堆优先队列的问题经常出现在面试的后半段尤其涉及海量数据、Top K、流式处理这些场景。核心思路是用一个固定大小的堆来维护需要关注的集合不用对所有数据排序。典型题目是“数组中的第K个最大元素”给定一个数组找出第K个最大的元素。最简单的方案是排序后取倒数第K个时间复杂度O(nlogn)。但这显然不是面试官想要的答案。用大小为K的最小堆遍历数组堆顶就是当前第K大的元素import heapq def findKthLargest(nums, k): heap [] for num in nums: if len(heap) k: heapq.heappush(heap, num) elif num heap[0]: heapq.heapreplace(heap, num) return heap[0]这样时间复杂度是O(nlogk)空间O(k)。如果k远小于n这个方案比全排序高效得多。如果面试官继续追问“数据量大到无法全部装入内存怎么办”思路也是一样的——数据规模大时堆的大小固定为k内存占用可控非常适合流式处理场景。这里还可以提一句快排的partition思想也能解决Top K问题时间复杂度平均O(n)但那是另一个进阶方向的考点了。3. 手撕代码的完整流程与细节从读题到ACM风格很多候选人有个误区觉得面试做题就是在白板上/在编辑器里把代码写出来就行了。实际上算法题面试考验的是从读题、分析、设计、编码、测试到复盘的全流程任何一步没做到位都会扣分。3.1 从读题到测试一个标准答题框架我建议所有人在面试前都要建立自己的答题框架不能拿到题就闷头写。我的框架是四步第一步是读题并确认理解。面试官出题后先用自己的话复述一遍题目明确输入输出格式、数据范围、边界条件。这一步看起来简单但特别容易出问题。比如题目说“数组长度最大10的5次方”这意味着O(n²)的算法很可能超时逼着你想O(nlogn)或O(n)的解法。第二步是给出暴力解法讲清复杂度。我见过很多候选人上来就想最优解结果卡住了。其实可以先说“这道题暴力做是两层循环枚举复杂度O(n²)然后我们发现这些重复的区间可以排序后用某种方式合并”先建立解题的基线再谈优化这让面试官能看到你的思考路径。第三步是编写代码并同步讲解。写代码时边写边解释每段逻辑的意义不要写完才讲。如果发现错误冷静修改不要紧张。实际上面试官更看重你修正错误的过程而不是你一次就写对。第四步是用测试用例验证。写完代码后主动说“让我用这个例子走一遍”然后手推一两个用例包括正常用例和边界用例。这一行为非常加分因为很多候选人写完就完了根本不验证。3.2 复杂度分析为什么每次都问有些候选人觉得复杂度分析就是个形式把标准答案背出来就行。其实不然面试官追问复杂度是在确认你是否真的理解自己写的代码怎么运作的。我举个实际例子。有次模拟面试候选人用Python写了一个“两数之和”的解法代码如下def twoSum(nums, target): for i in range(len(nums)): for j in range(i 1, len(nums)): if nums[i] nums[j] target: return [i, j]问复杂度他说O(n²)。问他“n的规模如果是10的4次方这个解法要跑多久”他开始没反应过来。这就是典型的“会背复杂度但没建立数据规模直觉”的情况。我建议每个人都要形成对数据规模的直觉时间复杂度数据规模上限约典型场景O(n)10^7 - 10^8数组遍历、滑动窗口O(nlogn)10^5 - 10^6排序、二分 遍历O(n²)10^3 - 10^4双层循环、部分DPO(2^n)20 - 25回溯、状态压缩DP这种直觉在面试时很有用。拿到题目后通过数据范围可以快速判断该用什么复杂度的算法这本身就是一种面试技巧。3.3 代码规范与边界判断手撕代码的时候代码规范也是隐形评分点。我面试时见过不少候选人思路完全正确但代码里满是“魔法数字”、变量名随意、缩进混乱这种代码看起来很吃力也会让面试官对你的工程素养产生怀疑。一些加分细节变量命名有意义不要用a、b、tmp满天飞边界条件先判断。比如数组为空、数组长度为1、目标值不存在等场景在写主逻辑之前先考虑注意Python里容易踩的坑比如负数取模、整除语义、下标越界等。我总结了一个“边界检查清单”面试写代码时会在心里过一遍输入为空或长度为0输入长度为1很多递归/循环逻辑在长度为1时会出问题数组已经有序或全逆序数组中全部是相同元素数值为负数、0、极大值注意整型越界Python虽然不限但其他语言要小心。这些检查做完代码的健壮性会明显提升。4. 高频失分点与排查技巧这些坑我都替你踩过写算法题面试的分享不能只讲怎么解对题还得讲讲那些“知道但没做到”的坑。每个坑我都自己踩过或者看别人踩过写出来帮大家避开。4.1 死循环与整型溢出的雷区先说死循环。最典型的场景是二分查找while left right和while left right的区别、mid left (right - left) // 2和mid (left right) // 2的区别这些细节决定你写的二分是死循环还是正确解法。举个例子二分查找的更新逻辑left, right 0, len(nums) - 1 while left right: mid left (right - left) // 2 if nums[mid] target: return mid elif nums[mid] target: left mid 1 else: right mid - 1这里如果left mid而不是mid 1当区间缩小到两个元素时就会死循环。我见过太多人在这种细节上栽跟头。还有一个是整型溢出问题。Python的int是任意精度的一般不担心溢出但如果你要写Java或C(left right) // 2在left和right都很大时可能溢出所以业界标准写法是left (right - left) // 2。很多候选人用Python写习惯了面试时说要用Java就会踩这个坑。4.2 递归栈溢出与空间优化递归虽然优雅但有一个隐蔽的缺点深层递归可能导致栈溢出。比如“二叉树的最大深度”如果树高度达到10的4次方或更高递归栈就会爆掉Python默认递归深度约为1000。面试中如果遇到递归最好主动提一句“如果递归深度特别深可以用迭代法改用显式栈”。这句话本身就是加分项说明你有工程思维而不只是会背书。做动态规划时空间优化也是个高频追问点。很多二维DP问题可以通过状态压缩从O(n²)空间压到O(n)甚至O(1)。比如“不同路径”这道题标准的DP解法开一个二维数组但实际上每个格子只依赖左边和上边的状态所以可以用一维数组滚动更新。这类“空间占用优化”的思考在面试中很讨喜。4.3 测试用例设计清单面试写代码很多人写完就结束了但真正专业的表现是主动设计测试用例。我习惯在脑子里准备一组测试用例覆盖正常情况、边界情况、异常输入。比如“三数之和”我的测试用例列表是这样的[-1, 0, 1, 2, -1, -4]——标准用例输出两个结果[0, 0, 0, 0]——所有元素相同只输出一个[0,0,0][-2, 0, 1, 1, 2]——存在重复的中间值[]和[0]——数组长度不足3直接返回空列表[1, 2, 3, 4]——没有任何三元组满足条件返回空列表。主动跑一遍这些用例很多隐藏bug就暴露了你的代码质量瞬间提升一个档次。这种“面向面试官展示思考过程”的习惯比多刷十道题都管用。4.4 面试心理与时间分配最后聊一点很多人忽略的东西面试中的时间分配和心理调节。算法题面试通常45到60分钟一般会安排1到2道题。我的建议是第一道题控制在20-25分钟内完成第二道题控制在20-30分钟。如果一道题卡了超过10分钟没有任何进展果断向面试官要提示。不要觉得开口要提示是丢脸的事面试官很多时候就是在等你主动求助——这也是考察沟通协作能力的一部分。另外写代码时的“出声思考”非常关键。不要闷头写代码你要让面试官看到你的思考过程。有一次我一个朋友面试题目是“判断链表是否有环”他想到了快慢指针但写代码时很紧张直接开始敲。面试官打断他问“你现在的思路是什么”朋友才意识到自己一直在沉默。后来他分享说从那以后每次刷题都强迫自己用口头描述思路入职后写代码前本先交流反而成了好习惯。还有一个小技巧如果代码写错了不要慌着删掉重写。先停下来看看是不是笔误如果是逻辑错误要冷静分析哪一步出了问题。面试官很看重这种纠错能力反而你慌慌张张地重写容易越改越乱。5. 后续进阶方向与实战建议系列第三篇的预告这个系列第二篇写到这高频题型基本覆盖了。但面试常考的算法题远不止这些还有一些进阶方向值得大家提前准备。如果这五大题型你已经掌握得不错建议往这几个方向扩展图论算法拓扑排序、并查集、Dijkstra与BFS的区别字符串处理KMP、Manacher、Trie树数论与位运算GCD、快速幂、异或技巧、状态压缩DP设计题LRU缓存、LFU缓存、并查集的启发式合并多线程与并发相关的手写题部分公司会出现。以LRU缓存为例这道题在系统设计面试和算法面试里都常考要求实现get和put操作时间复杂度都是O(1)。解法是用HashMap双向链表Python的OrderedDict是现成的工具。from collections import OrderedDict class LRUCache: def __init__(self, capacity): self.capacity capacity self.cache OrderedDict() def get(self, key): if key not in self.cache: return -1 self.cache.move_to_end(key) return self.cache[key] def put(self, key, value): if key in self.cache: self.cache.move_to_end(key) self.cache[key] value if len(self.cache) self.capacity: self.cache.popitem(lastFalse)这种题考的不仅是算法还有对数据结构组合使用的理解。面试官经常追问“为什么用双向链表”“为什么需要哈希表来辅助”“如果不用OrderedDict你能自己实现吗”你能把这些问题答好算法面试基本就稳了。还有一点经验想分享刷题不能光看题解一定要自己动手写。我在指导学弟学妹时发现很多人觉得“看懂了”就等于“会了”但到了面试现场一紧张手根本跟不上脑子。我自己的标准是一道题如果不能在20分钟内独立写出来并AC就不算真正掌握过两周再刷一遍。另外不要盲目追求题量。LeetCode刷到300题以上边际收益就开始递减了更重要的是反复咀嚼做过的题目把每道题背后涉及的多个知识点串成网络。我在面试中遇到新题时很多“新题”本质上是做过题的类型变体只要题型匹配快解题思路自然就有了。下次有机会我打算专门写一篇图论与并查集的实战分享把拓扑排序、最短路径、最小生成树这几块串起来讲配合真实面试题的拆解。如果大家有特别想看的题型也可以留言告诉我。
返回列表