作为一名常年混迹技术面试圈的老兵我这些年既当过候选人也坐在面试官的位置上看过太多人栽跟头。数据结构和算法这块说难听点大部分人的复习方式都是背题背到能默写防抖节流却写不出一个能处理边界条件的二分查找。说实话数据结构和算法面试题根本不考你会不会而是考你在压力下能不能把思路理清楚并写出健壮的代码。今天这篇东西我不打算给你堆一百道题的题海而是把面试中最常出现的核心题型、解题套路、复杂度陷阱和代码模板一次性捋清楚。无论你是准备春招秋招的在校生还是想跳槽涨薪的职场人这篇文章都是按面试官视角整理的。文章里每一类题我都会讲清楚它的识别特征、标准解法、进阶变体和常见失误点建议先收藏再按章节深入。1. 数组与链表的高频变形双指针、快慢指针与环形结构数组和链表是所有数据结构里最基础的两个恰恰也是面试官最爱做文章的。你以为他会考你反转链表那只是开胃菜。真正拉开差距的是双指针、快慢指针、环形结构这些一眼看不穿的变体。1.1 双指针的两大流派相向指针与同向指针双指针看起来简单很多人在LeetCode上刷了几十道双指针题但真到了面试现场让他讲讲为什么这题用双指针而不是哈希表往往就卡壳了。我建议你一定要分清两个流派。相向指针一个从左往右一个从右往左最经典的场景是有序数组的两数之和。比如给你一个升序数组和一个目标值让你找两个数使它们的和等于目标值。暴力解法是双重循环O(n²)而相向指针的时间复杂度是O(n)空间O(1)。关键思路是左指针指向最小值右指针指向最大值如果当前和大于目标说明需要减小就只能右指针往左移如果小了就让左指针往右移。public int[] twoSum(int[] nums, int target) { int left 0, right nums.length - 1; while (left right) { int sum nums[left] nums[right]; if (sum target) { return new int[]{left, right}; } else if (sum target) { left; } else { right--; } } return new int[]{-1, -1}; }这道题如果你用的是哈希表时间复杂度O(n)其实一样但空间复杂度O(n)面试官追问能不能O(1)空间的时候光靠哈希表就露馅了。所以有序数组两数之和的标准答案默认就是双指针这一点必须刻在脑子里。同向指针的核心场景是移除元素、去重、移动零这类原地操作题。经典题目是给你一个数组让你把值为0的元素全部移到末尾同时保持非零元素的相对顺序要求不能使用额外数组。答案是快慢双指针慢指针指向下一个该填入非零元素的位置快指针负责扫描整个数组。public void moveZeroes(int[] nums) { int slow 0; for (int fast 0; fast nums.length; fast) { if (nums[fast] ! 0) { nums[slow] nums[fast]; } } while (slow nums.length) { nums[slow] 0; } }我面试别人的时候这道题至少有四成候选人写出来的代码会出问题——他们不是不会思路而是忘了最后一步把后面位置补零。这个细节暴露出一个人对代码完整性的把控能力非常致命。1.2 快慢指针的环形检测从数学证明到代码实现链表有没有环这道题从入门到进阶全包了。最基础的解法就是快慢指针快指针每次走两步慢指针每次走一步如果有环它们一定会在某个节点相遇。这个结论很多人知道但被追问为什么就愣了。这里我给大家补一个直观的数学解释。假设链表无环部分的长度是a环的长度是b快指针和慢指针相遇时慢指针走了a k步k是它在环内走的距离快指针走了a m步且快指针比慢指针多走了n圈环的长度慢指针步数 a k 快指针步数 a m步数关系a m 2(a k) m - a 2k m a 2k因为快指针比慢指针的步数差一定是环长度b的整数倍也就是m - k (a 2k) - k a k 是b的倍数。慢指针走的步数a k正好是b的倍数意味着慢指针在环内绕了整数圈所以它们必然相遇。这个推导不用背面试时候能说个大概就行重点是让面试官相信你的结论有据可依。进阶问题找到环的入口才是真正的区分点。结论是从相遇点和链表头同时出发一个指针每次各走一步它们相遇的位置就是环入口。原理也不难设链表头到环入口距离为a相遇点距环入口为c沿着前进方向环长度为b。慢指针走了a c快指针走了a c nb 2(a c)可得a c nb即a nb - c。也就是说从链表头走到环入口的距离a等于从相遇点继续走nb - c步而nb相当于绕了n圈实际等价于走b - c步回到环入口。所以两个指针同时走必然在环入口碰到。1.3 LRU缓存的链表设计这题其实考的是数据结构组合LRU缓存Least Recently Used几乎是中大厂面试必考的题目。它要求设计一个get和put时间复杂度都为O(1)的数据结构。核心设计是哈希表 双向链表。为什么是双向链表而不是单向因为当缓存满了要淘汰末尾节点时单向链表无法O(1)地找到前驱节点来修改指针。双向链表配合哈希表就能做到get操作哈希表O(1)找到节点然后把节点移动到链表头部put操作如果key已存在更新value并移到头部如果key不存在插入头部超过容量时删除尾部节点class LRUCache { private HashMapInteger, Node map; private DoublyLinkedList list; private int capacity; public LRUCache(int capacity) { this.capacity capacity; this.map new HashMap(); this.list new DoublyLinkedList(); } public int get(int key) { if (!map.containsKey(key)) return -1; Node node map.get(key); list.moveToHead(node); return node.value; } public void put(int key, int value) { if (map.containsKey(key)) { Node node map.get(key); node.value value; list.moveToHead(node); } else { Node node new Node(key, value); if (map.size() capacity) { Node tail list.removeTail(); map.remove(tail.key); } list.addToHead(node); map.put(key, node); } } }这里的Node类要有prev和next两个指针链表要有虚拟头尾节点dummyHead和dummyTail来避免大量的空指针判断。我在代码评审里见过太多候选人栽在这上面直接用null判断去处理头尾边界不仅代码丑还容易漏情况。面试官真正想考察的不是你会不会背这个设计而是你有没有分析为什么是双向链表哈希表而不是数组哈希表的能力。数组也可以做到O(1)访问但插入/删除需要移动元素平均O(n)链表插入/删除是O(1)但查找要O(n)。哈希表解决了查找双向链表解决了有序维护两者组合才完美解决全部需求。2. 二叉树的两大灵魂递归本质与遍历的迭代写法二叉树是面试中的题量担当几乎可以占到所有算法题的三分之一。很多人觉得二叉树难本质原因是没有把递归吃透。而单考递归还不够面试官一定会追问能不能写成非递归这一问就能挂掉一大片人。2.1 为什么二叉树题95%可以用递归解——递归的实质二叉树的定义本身就是递归的左子树、右子树都是二叉树。所以但凡涉及树的遍历、深度、路径、构造递归都是最自然的解法。面试中你完全可以先用递归写出正确解法思路清晰后如果面试官追问空间复杂度再做非递归优化。一个典型的例子是求二叉树的最大深度public int maxDepth(TreeNode root) { if (root null) return 0; return Math.max(maxDepth(root.left), maxDepth(root.right)) 1; }三行代码看起来简单。但真正理解递归的人会知道这题考的是分治思想求一棵树的最大深度 左子树最大深度和右子树最大深度之间的最大值 1。每一层递归都只关心当前节点和它的左右子树不需要关心全局。这就是递归的思维方式——用局部推导整体。我见过很多候选人在纸上画递归的调用栈非要模拟整个过程把简单问题复杂化。要是你面试候也是这么干的请你立刻停止。理解递归不应该靠模拟而应该靠信任——相信递归函数能正确处理子问题。2.2 三种遍历的递归与非递归栈模拟的本质面试考二叉树遍历特别爱考非递归写法。先说结论任何递归都能用栈模拟因为递归调用本身就是函数调用栈。前序遍历非递归的写法最简单用栈先压右子树再压左子树弹出的时候访问节点。中序遍历非递归的核心是不断往左走并入栈走到底之后弹出节点访问然后转向右子树。后序遍历非递归相对麻烦常见技巧是用两个栈或者在节点结构里加标记。我用中序遍历来展示一个非常优雅的标记法思路public ListInteger inorderTraversal(TreeNode root) { ListInteger result new ArrayList(); StackObject stack new Stack(); if (root ! null) stack.push(root); while (!stack.isEmpty()) { Object obj stack.pop(); if (obj instanceof TreeNode) { TreeNode node (TreeNode) obj; if (node.right ! null) stack.push(node.right); stack.push(node.val); if (node.left ! null) stack.push(node.left); } else { result.add((Integer) obj); } } return result; }这个思路的本质是遇到一个节点的时候先不要急着访问而是把右节点、当前值标记、左节点按逆序压栈。这样弹出的时候左子树先处理、再到当前值、最后右子树自然形成中序。这种加标记的方法对前中后序遍历完全统一理解一次三种遍历的迭代写法全都会了。2.3 二叉树的层序遍历BFS的模板化写法层序遍历用队列这是BFS的基本思想。面试真题往往不会直接给按层输出一个数组而是给各种变形比如之字形遍历、右视图、二叉树的最小深度。之字形遍历Zigzag是最高频的变形之一。要求第一层从左到右、第二层从右到左、第三层从左到右依次输出。写法上可以在普通层序遍历基础上加一个反转标记每层结束时切换方向public ListListInteger zigzagLevelOrder(TreeNode root) { ListListInteger result new ArrayList(); if (root null) return result; QueueTreeNode queue new LinkedList(); queue.offer(root); boolean leftToRight true; while (!queue.isEmpty()) { int size queue.size(); LinkedListInteger level new LinkedList(); for (int i 0; i size; i) { TreeNode node queue.poll(); if (leftToRight) { level.addLast(node.val); } else { level.addFirst(node.val); } if (node.left ! null) queue.offer(node.left); if (node.right ! null) queue.offer(node.right); } result.add(level); leftToRight !leftToRight; } return result; }注意这里的关键技巧借助LinkedList的addLast和addFirst来决定该层的添加顺序而不是真的把层结果反转这样效率更高。层序遍历的模板是先记录当前层size再一次性处理这size个节点这个模板值得烂熟于心几乎所有BFS类题目都套这个框架。2.4 最近公共祖先与路径类题目递归返回值的巧妙设计最近公共祖先LCALowest Common Ancestor是面试中的常青树。给定二叉树和两个节点p、q找到它们的最近公共祖先。这类题的核心思路是递归函数返回什么定义一个包含性的语义。public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root null || root p || root q) return root; TreeNode left lowestCommonAncestor(root.left, p, q); TreeNode right lowestCommonAncestor(root.right, p, q); if (left ! null right ! null) return root; return left ! null ? left : right; }这段代码的精妙在于左子树和右子树如果都返回非空说明p和q分别位于当前节点的两侧答案就是当前节点如果只有一侧非空那答案在那一侧。这个设计思路特别有价值——递归函数的返回值不只是结果还可以是子问题的部分答案。再比如路径类题目二叉树中路径和等于目标值的所有路径就得用到回溯思想递归进入时添加节点递归退出时删除节点。核心是维护一个path列表在递归的不同分支中不要互相污染。3. 二分法与排序变体边界条件才是真正的考点二分查找每个人都会写但一写就错的人也是最多的。我面试时很喜欢考察二分因为二分查找的代码只有几行恰恰是暴露候选人编码习惯的最佳试金石。3.1 二分查找的统一写法左闭右开还是左闭右闭很多人的二分写法混乱就乱在边界条件上。这里我给一个建议要么始终用左闭右闭要么始终用左闭右开不要混。我个人的习惯是左闭右闭[left, right]配合while (left right)。public int binarySearch(int[] nums, int target) { int left 0, right nums.length - 1; while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { return mid; } else if (nums[mid] target) { left mid 1; } else { right mid - 1; } } return -1; }这里有个经典陷阱int mid (left right) / 2;在极端情况下会溢出。虽然现代面试通常不会让你真跑到20亿长度的数组但写成left (right - left) / 2是专业的做法这个细节能加分。更重要的是**把问题转化为二分答案**的能力。比如找到第一个不小于target的位置、找到最后一个小于target的位置这些变体本质上是在考察你能不能调整边界条件和比较逻辑。我面试过不少人能把标准二分背出来但换一个问法就懵了。其实只需要记住一个原则**当nums[mid]满足条件时收缩哪一侧**这个问题的答案直接决定你的代码长什么样。3.2 快速排序与快速选择partition的艺术排序算法里快速排序几乎必考但考的不是写代码而是partition过程的原理和应用。快排的partition函数能做到选一个pivot把数组分成左小右大并返回pivot的最终下标。快速选择QuickSelect就是利用partition实现O(n)时间求第k大元素或第k小元素。public int findKthLargest(int[] nums, int k) { int targetIndex nums.length - k; int left 0, right nums.length - 1; while (true) { int pivotIndex partition(nums, left, right); if (pivotIndex targetIndex) { return nums[pivotIndex]; } else if (pivotIndex targetIndex) { left pivotIndex 1; } else { right pivotIndex - 1; } } }为什么要写nums.length - k因为第k大对应升序排序后下标为len - k的位置。这里考生常见的错误是分不清第k大和第k小的下标换算。partition的实现方式有很多种面试推荐用Lomuto分区代码短、清晰最不容易写错private int partition(int[] nums, int left, int right) { int pivot nums[right]; int i left; for (int j left; j right; j) { if (nums[j] pivot) { swap(nums, i, j); i; } } swap(nums, i, right); return i; }快速选择的平均时间复杂度是O(n)最坏是O(n²)。如果面试官追问最坏情况怎么优化标准答案是随机化选取pivot这就够了。3.3 归并排序不仅仅是排序归并排序的链表版本太常考了——合并两个有序链表和排序链表。归并排序的思想本身就是分治把一个大问题分成两个小问题各自解决后再合并。链表排序为什么首选归并而不是快排因为链表不支持O(1)随机访问快排的partition需要频繁随机访问效率低归并排序只需要顺序遍历更适合链表。public ListNode sortList(ListNode head) { if (head null || head.next null) return head; ListNode slow head, fast head.next; while (fast ! null fast.next ! null) { slow slow.next; fast fast.next.next; } ListNode mid slow.next; slow.next null; ListNode left sortList(head); ListNode right sortList(mid); return mergeTwoLists(left, right); }注意找链表中点用快慢指针fast初始化为head.next而不是head这样当链表只有两个节点时不会陷入死循环。这是一个极其细但是极其关键的边界细节面试时写错的人在多数。4. 动态规划的六个破题步骤状态定义决定成败动态规划是算法面试的大魔王。说句实在话很多人刷了几十道DP题还是不会做新题原因就是没掌握方法论。动态规划的面试解题不是靠灵感而是有一套可以刻意练习的分析流程。4.1 动态规划的六步破题法我在面试中总结了一个六步法用来解决绝大多数的DP类题目分享给你明确状态定义这一步决定所有后续推导必须写清楚dp[i]或dp[i][j]代表什么含义。比如dp[i]表示以第i个元素结尾的最大子数组和而不是模糊的前i个元素的最大值。确定状态转移方程从上一个状态如何推导到当前状态。确定初始化和边界条件dp[0]或者dp[0][0]是什么。确定遍历顺序从上到下、从左到右还是需要特殊顺序。模拟推导一个小例子拿一个简单输入手推一遍验证转移方程正确。思考状态压缩如果dp[i]只与dp[i-1]有关能不能用滚动数组把空间从O(n)降到O(1)。这里最容易出问题的是第一步。一个反例是最长递增子序列——这题的定义必须是dp[i]表示以第i个元素结尾的最长递增子序列长度而不是前i个元素中的最长长度。后者无法建立转移方程因为你想知道能不能接上就必须知道当前最后一个元素是谁。4.2 背包问题大总结0-1背包、完全背包、初始化细节背包问题是动态规划里最经典的模板化题型。面试常见的变形是0-1背包和完全背包。0-1背包每件物品只能选一次。典型题目是分割等和子集。public boolean canPartition(int[] nums) { int sum 0; for (int num : nums) sum num; if (sum % 2 ! 0) return false; int target sum / 2; boolean[] dp new boolean[target 1]; dp[0] true; for (int num : nums) { for (int j target; j num; j--) { dp[j] dp[j] || dp[j - num]; } } return dp[target]; }注意内层循环从target倒序遍历到num这样才能保证每件物品只被使用一次。如果你把它写成正序遍历就会退化成完全背包——同一件物品可以被重复取用。这个细节在面试现场经常被追问很多候选人说不清楚。完全背包每件物品可以选无限次。典型是零钱兑换。内层循环必须是正序public int coinChange(int[] coins, int amount) { int[] dp new int[amount 1]; Arrays.fill(dp, amount 1); dp[0] 0; for (int i 1; i amount; i) { for (int coin : coins) { if (i coin) { dp[i] Math.min(dp[i], dp[i - coin] 1); } } } return dp[amount] amount ? -1 : dp[amount]; }这道题的思路本质是要凑出金额i可以先凑出金额i-coin然后加一枚coin所以dp[i]的值可以从所有dp[i-coin]推导。这里Arrays.fill(dp, amount 1)用一个大数当无穷大因为任何金额最多用amount枚硬币就能凑出1元硬币。这是一个非常值得记住的初始化套路。4.3 区间DP与股票问题把大问题拆成小问题股票买卖系列是动态规划的变形题集。它考的不是你会不会背而是能不能理解状态机这个工具。以买卖股票的最佳时机可以多次交易但有冷冻期为例我们可以定义三个状态hold[i]第i天结束后持有股票的最大利润sold[i]第i天结束后不持有股票且处于冷冻期的最大利润rest[i]第i天结束后不持有股票且不在冷冻期的最大利润转移方程就是状态图的翻译今天持有 昨天就持有 / 昨天休息然后今天买入今天冷冻 昨天持有然后今天卖出今天休息 昨天冷冻 / 昨天休息这类题的共同点是状态有限且转移逻辑清晰把状态图画出来代码就是照着翻译。面试时候如果遇到没见过的DP题画状态机是一个非常有效的破题手段。4.4 记忆化搜索当递推不好想用递归带着缓存记忆化搜索也是DP的另一种写法特别适合那些状态转移方向不直观的题目。典型场景是计算字符串s1和s2交错组成s3这类验证型的DP。记忆化搜索的核心是递归函数dfs(i, j)表示用s1的前i个字符和s2的前j个字符能否组成s3的前ij个字符。然后加一个memo数组缓存访问过的状态。public boolean isInterleave(String s1, String s2, String s3) { if (s1.length() s2.length() ! s3.length()) return false; Boolean[][] memo new Boolean[s1.length() 1][s2.length() 1]; return dfs(0, 0, s1, s2, s3, memo); } private boolean dfs(int i, int j, String s1, String s2, String s3, Boolean[][] memo) { if (memo[i][j] ! null) return memo[i][j]; if (i s1.length() j s2.length()) return true; if (i s1.length() s1.charAt(i) s3.charAt(i j) dfs(i 1, j, s1, s2, s3, memo)) { memo[i][j] true; return true; } if (j s2.length() s2.charAt(j) s3.charAt(i j) dfs(i, j 1, s1, s2, s3, memo)) { memo[i][j] true; return true; } memo[i][j] false; return false; }记忆化搜索和递推DP的时间复杂度理论上一样但空间开销略大一些胜在写起来更直接不容易出错。面试中不确定递推顺序时先写记忆化搜索往往比硬推递推公式更稳。5. 哈希表与字符串隐藏的时间复杂度与滑动窗口技巧哈希表是万金油几乎所有需要快速查找的场景都可以用它偷空间换时间。但面试官不是让你无脑用哈希表他会追问你还能优化吗、能不能不用哈希表这时候就考验你对自己方案的复杂度理解有多深。5.1 两数之和的全家桶从哈希表到无序数组两数之和的经典解法是哈希表存数值-下标一遍遍历找到目标差值就返回。这是O(n)时间O(n)空间。如果你遇到的是有序数组前面讲过可以用双指针优化到O(1)空间。还有一种变形是三数之和这题在两数之和的基础上加了去重的要求哈希表反而不好做了。标准解法是排序 双指针固定一个数剩下的子数组用相向双指针找两数之和为-nums[i]。去重的方法是外层循环跳过重复的固定值内层双指针找到一组结果后也要跳过重复的左右值。public ListListInteger threeSum(int[] nums) { ListListInteger result new ArrayList(); Arrays.sort(nums); for (int i 0; i nums.length - 2; i) { if (nums[i] 0) break; if (i 0 nums[i] nums[i - 1]) continue; int left i 1, right nums.length - 1; while (left right) { int sum nums[i] nums[left] nums[right]; if (sum 0) { result.add(Arrays.asList(nums[i], nums[left], nums[right])); while (left right nums[left] nums[left 1]) left; while (left right nums[right] nums[right - 1]) right--; left; right--; } else if (sum 0) { left; } else { right--; } } } return result; }这题面试官真正关注的是去重逻辑。如果你写的代码里只有一个while去重那很可能有遗漏。边界条件是外层循环固定值去重时必须continue跳过而不是i内层双指针去重时要同时处理left和right两侧的重复。5.2 滑动窗口算法从暴力到最优的完整推导滑窗是字符串类题目里最常考的技巧。典型题目包括最小覆盖子串无重复字符的最长子串字符串的排列。以无重复字符的最长子串为例标准的题解框架是右指针不断右移扩大窗口每当遇到重复字符左指针就收缩窗口直到没有重复。这里的关键是维护一个HashMapCharacter, Integer记录字符的最后出现位置。public int lengthOfLongestSubstring(String s) { MapCharacter, Integer lastIndex new HashMap(); int maxLength 0; int left 0; for (int right 0; right s.length(); right) { char c s.charAt(right); if (lastIndex.containsKey(c) lastIndex.get(c) left) { left lastIndex.get(c) 1; } lastIndex.put(c, right); maxLength Math.max(maxLength, right - left 1); } return maxLength; }注意这里判断重复的条件不光是containsKey还要加上lastIndex.get(c) left因为如果这个字符上次出现的位置已经在这个窗口的左边界之前它不影响当前窗口的字符唯一性。这是最容易被忽略的细节很多人在不区分哈希表里存了和出现在当前窗口内这两个概念。滑动窗口的时间复杂度是O(n)每个字符最多被左指针和右指针各访问一次。面试时如果能把时间复杂度为什么是O(n)而不是O(n²)说清楚面试官会认可你是真懂了。5.3 分组异位词哈希表键值设计的思考给你一堆字符串把字母异位词分组比如eat、tea、ate是一组。经典思路是用排序后的字符串作为哈希表的key同一个异位词组的所有字符串排序后是一样的。public ListListString groupAnagrams(String[] strs) { MapString, ListString map new HashMap(); for (String str : strs) { char[] chars str.toCharArray(); Arrays.sort(chars); String key new String(chars); map.computeIfAbsent(key, k - new ArrayList()).add(str); } return new ArrayList(map.values()); }这题的时间复杂度是O(n * k log k)其中k是最长字符串的长度。如果要求更优可以用26个字母的计数数组当key时间复杂度降到O(n * k)。面试官追问优化的时候给出这个思路会加分。6. 面试实战答题路径、复杂度分析与临场心态最后这部分我想聊点面试代码之外的东西。数据结构算法面试写对代码只是及格线能不能让面试官觉得这人有水平靠的是完整的答题路径和沟通方式。6.1 拿到题目的标准思考路径我梳理出一套拿到题不看答案先想什么的流程推荐给你先花1-2分钟理解题目把输入输出、边界条件、数据规模说清楚。尤其是数据规模它直接决定了你的算法复杂度级别能不能过。先说暴力解法再做优化哪怕是O(n²)的暴力先说清楚让面试官知道你有思路。给出最优解之前说明为什么不用其他解法比如这题可以用哈希表但空间O(n)如果要求O(1)空间就得排序双指针。这种对比思维是加分项。写代码前先说算法框架不用直接写代码而是先描述我会用双指针左指针从0开始右指针扫描数组遇到重复就移动左指针。框架讲清楚了代码就不会跑偏。写完代码后主动测试不要等面试官提醒自己给出两三个测试用例包含边界情况比如空数组、长度1、全是重复元素。跑一遍代码并口述输出这步很多人忽略其实特别重要它能证明你对自己的代码有完全掌控。6.2 复杂度分析的表达模板面试时被问你这个算法的时间复杂度和空间复杂度是多少很多人的回答是我觉得是O(n)这种回答完全没有说服力。我建议你按这个模板回答时间复杂度把代码的每个主要循环拆开说清楚外层循环O(n)、内层循环虽然嵌套但不是每次都是完整n均摊下来每个元素只被访问两次所以总体O(n)。空间复杂度说清楚额外开了多大的数据结构比如开了一个哈希表最多存n个key所以O(n)如果是原地操作就说是O(1)。我遇到过很多候选人代码写对了但问时间复杂度支支吾吾。这很可惜。实际上复杂度分析是有迹可循的每道题写完代码后花30秒在脑子里过一遍循环嵌套结构基本都能答对。6.3 现场编码的三条铁律第一个铁律是先沟通后编码。不要面试官题目还没说完就开始写先复述一遍题目确认理解没有偏差。第二个铁律是别怕用暴力解打底。很多人追求一步到位写最优解结果写到一半卡住心态崩了。稳妥的策略是先写一个能跑的解法哪怕是暴力跑通之后再说这个解法是O(n²)我想优化到O(n)面试官反而会欣赏你的渐进思维能力。第三个铁律是边界条件写在显眼位置。比如数组为空、长度为1、目标值不存在这些情况在编码一开始就处理掉能够大幅减少代码的复杂分支。6.4 面试后的复盘方法面试结束后的复盘其实比面试本身更重要。我这里分享一个三层复盘法第一层重新把题目做一遍确认面试中卡住的地方。第二层总结这题属于哪种模式——是双指针、滑窗、DP还是图遍历模式归类比题目本身更有价值因为下次遇到类似的题你调用的是模式。第三层给自己出一道变体题。比如面试考了两数之和有序版你就想想如果加一个不能使用额外空间的限制呢如果目标值换成三元组呢这种变体训练一个月下来比盲目刷一百道题管用得多。我在带新人刷题的时候最常说的一句话是面试中最重要的不是你恰好撞到了原题而是你面对新题时有一套稳定的分析框架。这套框架就是今天文章里贯穿始终的东西——先识别题型模式再套对应解题模板最后通过边界条件验证。数据结构和算法的准备是一个慢变量平常刷题的时候养成写注释、分析复杂度、反思替代方案的习惯等真正坐到面试官对面的时候你会发现很多紧张感会自然消失因为你已经在心理上把被考察转化成了分享自己的解题思路。希望这篇整理能成为你的案头参考遇到忘记的题随时翻一翻。
阅读完成 · 觉得有帮助?