
1. 写在前面为什么算法题成了双端开发者的“生死线”这两年面 Android 和 Flutter 岗位我最大的感受是算法考察的权重明显在上升而且考察方式越来越“不讲武德”。以前你背背八股文讲讲 Activity 启动模式、讲讲 Flutter 的 Widget 树和 Element 树的区别基本能撑到 HR 面。现在不行了很多公司第一轮就是在线 coding直接甩一道中等难度的题让你在共享编辑器里写边写边讲思路。KMP、LRU、手写快排、二叉树遍历这些已经成了高频考点。我整理这篇面经不是想教你“背题”而是想聊清楚一件事在 Android 和 Flutter 双端背景下算法面试到底在考什么以及你怎么准备才能不白费力气。从算法本身来看双端开发者在准备面试时有一个独特优势你接触过的真实业务场景比其他方向更丰富。列表滑动卡顿、图片缓存淘汰、文本搜索高亮、嵌套滚动冲突——这些日常开发里踩过的坑本质上都是算法问题。面试官其实很喜欢问“你项目里哪里用到了数据结构”如果你能从实际项目出发去反推算法题记忆深刻得多答出来也更有说服力。这篇文章适合谁正在准备 Android 或 Flutter 岗位跳槽的人尤其是那种“算法基础一般、但项目经验还不错”的开发者。我会把高频考点、双端场景的结合点、以及我在面试中被问过的真实题目和踩坑经历都放进来尽量做到能直接照着准备。2. 整体拆解面试官眼里的“算法能力”到底是什么2.1 从五次真实面试反推出的考察逻辑我统计了过去半年里五次技术面三家一线大厂、两家中厂的算法题分布结果很有意思面试轮次题目类型题量难度考察侧重点一面电话面字符串/数组操作1题简单~中等基础扎实度、边界思维二面视频面哈希表 手写数据结构1~2题中等工程实现能力三面交叉面/主管面二叉树/动态规划1题中等偏难逻辑推导能力、深度加面可选系统设计 算法扩展1题不定综合架构能力发现没有面试官并不是要考倒你而是通过算法题快速判断你的代码风格、边界敏感度和逻辑推导能力。你能不能用最朴素的解法先把题做出来然后主动提出优化再分析时间空间复杂度——这套流程比“直接秒杀最优解”更让面试官有好感。尤其要提到的是热词里出现的KMP 算法中对于模式串 pabacaba其 next 数组这道题我确实在面试中遇到过变种。面试官不一定让你写出完整 KMP 代码但会问你“next 数组里存的是什么”“失配时怎么跳转”。如果你能把前缀后缀的概念讲清楚再用一段代码把 next 数组的求法写出来这道题基本就是送分题。2.2 Android 开发和 Flutter 开发在算法准备上的差异点纯 Android 开发者的算法准备重点应该放在Java/Kotlin 集合框架的底层实现上。比如HashMap的哈希冲突处理、扩容时机、红黑树退化条件这些既是八股文也是算法题。面试官随手就能把HashMap和“手写一个 LRU Cache”串起来问因为LinkedHashMap本身就实现了accessOrder模式下 LRU 的能力你能说出来就是加分项。而 Flutter 开发者要额外注意的是Dart 语言的特性和 Flutter 框架的性能敏感点。Dart 的List底层是可变长数组insert(0, item)的时间复杂度是 O(n)如果你在面试中写 Flutter 相关代码时用了这个操作可能会被追问“如何优化”。Flutter 的ListView.builder懒加载机制背后是“视口 缓存区”的算法设计面试官可能会问你对大数据量列表的优化思路这时候你用“分页 虚拟化 预加载”的算法思维去回答会非常加分。不过说实话双端经验的开发者有一个很大的优势你的算法知识可以被两个端“共用”。一道链表题你可以用 Kotlin 写一遍再用 Dart 写一遍两种语言实现对比着讲面试官会认为你理解的是算法的本质而不是背语言 API。我在面试中做过类似展示反馈普遍比较好。3. 高频考点精讲刷题不能只刷“数量”更要刷“场景”3.1 字符串与数组最容易被低估的送分题字符串和数组的题目通常被安排在面试第一轮难度不高但淘汰率不低。原因很简单字符串和数组的边界条件太容易出错了。空串、越界、首尾元素、负数索引这些“魔鬼细节”往往就是一道题能否通过全部测试用例的关键。以一道高频题“合并两个有序数组”为例。常规做法是双指针从后往前遍历这样可以在原地合并不需要额外空间。很多人一上来就从前开往开始找结果需要移动元素时间复杂度直接飙到 O(n²)。这种题考察的不是你会不会 merge而是你有没有“从后往前”的逆向思维。热词里还有“排序算法”频繁出现我建议把冒泡排序、快速排序、归并排序三种都手写一遍尤其是快排的 partition 操作。面试考排序的时候往往不是单纯让你写算法而是让你分析稳定性、时间复杂度和适用场景。我梳理了一个对比表格面试前看这个比自己翻书高效排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性适用场景冒泡排序O(n²)O(n²)O(1)稳定近乎有序的小数组快速排序O(n log n)O(n²)O(log n)不稳定通用场景注意选主元归并排序O(n log n)O(n log n)O(n)稳定大规模外部排序、链表排序Flutter 场景下字符串题还有一个变种值得注意如何高效处理 Dart 中的字符串不可变特性。Dart 中String是不可变的一旦拼接就产生新对象。如果你用循环str item去拼接大量字符串性能会非常差。此时应该用StringBuffer。这个点看似跟算法无关但如果你在算法题里用了低效的 String 拼接方式面试官可能会当场让你分析复杂度然后引导你用StringBuffer优化——这就从“算法题”变成了“工程能力题”答得好会非常加分。3.2 链表与哈希表高频之王考察“指针敏感度”链表题几乎是每次面试必出的大类。反转链表、环形链表检测、合并两个有序链表、删除倒数第 N 个节点——这些题目都有清晰套路。准备链表题时我强烈建议你养成两个习惯第一一定要画图第二在代码里多声明几个命名清晰的指针变量比如prev、curr、nextTemp不要用p1、p2这种一眼看不懂的名字。面试官会通过你的变量命名来判断你的代码可读性这也是一种隐形的考察。哈希表就更不用说了。“两数之和”是 LeetCode 第 1 题几乎人人都会但面试官会变形“如果数组是排好序的呢”——那就是双指针解法O(1) 空间搞定。“如果要求返回所有不重复的组合呢”——那就需要考虑去重。同一个题根可以变异出很多种问法核心是你要理解哈希表“用空间换时间”的本质。热词里出现的“贪心算法”和“动态规划”我放在后面的部分专门讲。这里先说一个规律——一线大厂对哈希表的考察经常结合“手写 LRU Cache”这类工程实现题。这题用“哈希表 双向链表”来做核心在于每次访问或插入时维护节点顺序。有 Android 开发经验的候选人可以主动提到LinkedHashMap在图片缓存如 Glide 的 LruCache中的应用有 Flutter 经验的则可以提到dart:collection里的LinkedHashMap也支持accessOrder我记得 Flutter 的内存缓存也有类似的实现思路。把算法和工程结合起来讲面试官会默默给你加分。3.3 二叉树与递归考察“抽象建模能力”二叉树题目是面试分水岭。一面考链表哈希很多人能过二面考二叉树会刷掉一批人。二叉树题的核心不是“背遍历模板”而是你能不能把问题抽象成“左子树做什么 右子树做什么 当前节点做什么”这三个子问题。二叉树的最大深度、最近公共祖先、层序遍历、验证二叉搜索树——这几道题做熟大多数二叉树题都能找到思路。我特别想强调的是递归出口和递归返回值的设计。很多人在写二叉树递归时总是搞不清返回值应该是什么。一个简单实用的方法先想清楚“我要从子问题中获取什么信息”然后把这个信息作为返回值类型。比如求最大深度返回值是 int代表子树高度判断一棵树是否平衡你既要返回“是否平衡”又要返回“高度”这时可以封装一个结果类或者用特殊值如 -1 表示不平衡来合并信息。Flutter 开发者在准备二叉树时有一个天然优势Widget 树本身就是一棵树结构。面试官可能追问“Flutter 如何高效的遍历 Widget 树”或者“如何判断两棵 Widget 子树是否相同”。这时候你可以把Widget的canUpdate方法和“相同子树判定”结合起来回答非常加分。我在面试中把Element树的更新机制类比成“diff 算法”再把二叉树层序遍历的队列实现套到 Flutter 的“脏节点收集”上面试官直接跳过后续追问进入了下一个环节。3.4 动态规划与贪心不会就跳过不你必须会套路动态规划是很多人面试时的噩梦但说实话面试中动态规划考来考去就那么几个模型背包模型、最长递增子序列、最长公共子序列、爬楼梯斐波那契变体、打家劫舍、零钱兑换。如果你时间有限优先把这几类背熟再去扩展。准备动态规划题我自己的方法论是五步走定义状态明确 dp[i] 或 dp[i][j] 代表什么这步错了全盘皆输确定转移方程写清楚当前状态如何从之前的状态推导出来初始化确定边界状态的值确定遍历顺序是正着遍历还是倒着遍历一维还是二维空间优化能否滚动数组节省空间以经典的“打家劫舍”为例dp[i]表示抢劫到第 i 个房屋时能获得的最大金额转移方程是dp[i] max(dp[i-1], dp[i-2] nums[i])。这个方程的本质是“偷还是不偷”两个状态取最优。能把这个逻辑讲明白比写出代码更重要。贪心算法的准备思路不太一样。贪心不需要动态转移方程但它需要你证明“局部最优能推出全局最优”。面试时就算你思路正确面试官也会追问一句“为什么贪心是正确的”。如果你回答不上来很容易被认为是“猜的”。所以准备贪心题时一定要先学会证明用反证法、交换论证法这些基础证明技巧。热词里的“粒子群算法”本质上也是一种启发式搜索算法和贪心有一定关联面试中直接考的概率极小但如果你能把它和 Flutter 动画曲线优化结合起来讲会很惊艳。4. 双端场景实战把算法题“翻译”成你的日常开发4.1 Android 高频场景从 LruCache 到 Binder 线程池Android 开发者在面试时算法题往往不是孤立出现的而是会结合系统组件来问。最经典的场景是LruCache。LruCache内部使用LinkedHashMap实现accessOrder为 true 时每次get都会把访问的节点移动到链表尾部插入时如果超过maxSize就把链表头部的节点移除。这就是 LRU 淘汰策略。面试官如果让你“手写一个 LRU Cache”你可以先写基于LinkedHashMap的版本然后问面试官“是否需要我自己实现双向链表”。如果对方点头你再从零实现展示你对哈希表和链表操作的综合掌握。我当时就是这么干的面试官明显对我的“先简单后复杂”的思路表示认可。另一个高频结合点是线程池的任务调度算法。Android 的ThreadPoolExecutor内部用BlockingQueue来缓存任务当核心线程数满了新任务进入队列等待当队列满了且线程数未达到最大线程数创建新线程如果线程数已达最大且队列已满执行拒绝策略。这个机制如果从算法角度去回答其实就是“生产者-消费者模型 有界队列 饱和策略”。面试官听到你用算法视角去拆解线程池会认为你的知识体系是打通的。排序算法在 Android 场景下的极致体现就是RecyclerView中的DiffUtil。DiffUtil内部使用 Myers 差分算法核心思想是在两个列表之间寻找最短的编辑路径插入、删除、移动的最小操作数。这比全量刷新性能高很多。如果你能在面试中把这个原理讲出来再顺手提一下自研AsyncListDiffer做异步差量计算的思路面试官基本就会在算法这一栏打钩了。4.2 Flutter 高频场景从列表懒加载到文本渲染的“隐藏算法”Flutter 面试中算法题的呈现方式更加隐晦。比如这道高频题“为什么ListView.builder比ListView性能好”答案是ListView.builder采用了懒加载策略只构建当前视口viewport内可见的 Widget加上缓存区cacheExtent内的少量项。当你滑动时Flutter 复用已销毁的 Widget 的 Element更新 RenderObject 数据而不是重新创建——这个过程本质上就是“生产者-消费者 对象池”的算法思想。文本渲染场景也很有意思。Flutter 的TextField在输入中文时需要处理“组合态”文本拼音输入法还未确定候选字这时候的输入处理逻辑本质上是一种“状态机”算法。虽然极少有面试官直接考这题但你如果能在简历中写“处理过 Flutter 输入框的组合态兼容”面试官大概率会追问“底层的输入法是怎么跟 Flutter 通信的”涉及到的TextInputConnection和TextEditingValue其实就是一个缓存同步问题。热词中的“Flutter 生命周期”也是高频面试点但你可能想不到生命周期也能和算法挂钩。我在面试中被问到“Widget 树重新 build 时如何避免不必要的子 Widget 重建”这个问题表面上是性能优化实际上是“记忆化memoization”算法思想。shouldRepaint、const Widget、RepaintBoundary这些都是为了避免重复计算和重复绘制——和动态规划中“避免重复子问题”的思路如出一辙。4.3 混合开发场景JSBridge 和通信协议里的算法思想拥有混合开发经验的候选人面试官可能会问Flutter 和原生通信的实现机制。MethodChannel底层使用StandardMethodCodec做二进制序列化它会把 Dart 对象编码成字节流再在原生端解码。如果你学过“哈夫曼编码”或“变长编码”的思想就能理解为什么StandardMethodCodec的类型标记设计得如此紧凑——它使用一个字节表示类型标识并根据类型选择负载长度这样能大幅减少传输体积。Android 端WebView的 JS bridge 也同样如此。addJavascriptInterface暴露给 JS 的接口底层实际上是通过“注解 反射 参数编解码”来实现的。如果你研究过它的源码你会发现这其实就是一种“命令模式 协议解析”跟算法中的“状态机解析器”类似。面试时如果能把通信协议的编解码设计讲成一种算法思考会给自己贴上“有深度”的标签。5. 面试实战技巧怎么从“会做题”变成“会表达”5.1 在线 coding 的“黄金五分钟”开场面算法面试的关键节点是拿到题目后的前五分钟。很多人的习惯是马上提笔写这是大忌。我的建议是先花 1-2 分钟复述题目、确认边界条件再花 2-3 分钟和面试官口头沟通思路等对方确认后再动手写。具体可以这样说“我先确认一下要求数组里如果有重复元素结果需要去重吗返回的是下标还是值”“我的初步思路是用哈希表先遍历一遍存下每个数字的下标再遍历一次查找差值时间复杂度 O(n)空间复杂度 O(n)。如果您希望进一步优化空间我可以先排序再用双指针但那样时间复杂度会变成 O(n log n)。您看我这个思路可以吗”上面这段话涵盖了三层价值确认需求、展示思路、提供备选方案。面试官听了会认为你是一个“先思考后动手”的工程师而不是一个“刷题机器”。我在模拟面试中发现能够主动复述题意的候选人通过率普遍高于直接写代码的人。5.2 在“不会做”的情况下如何自救面试过程不可能一帆风顺总有卡住的时候。你可能会遇到一道没见过的题目第一反应是大脑空白。这时候最忌讳的是沉默。面试官宁可听到你说“我暂时没有思路但我可以尝试从暴力解开始”也不希望你在那里硬憋 10 分钟不说话。我的自救套路是从暴力解开始先写一版所有情况都考虑的解法哪怕是 O(n³) 的复杂度。先让代码能跑再谈优化。尝试降低规模把数据规模缩小到 3 个元素手动跑一遍暴力解观察中间过程看能不能发现可递推的规律。主动抛出复杂度问题如果暴力解通过后还有时间主动说“这段代码时间复杂度是 O(n³)我可以尝试用哈希表把它降到 O(n²)再通过排序降到 O(n log n)”。即使优化不完美面试官也能看到你的思维过程。承认不足并快速反打如果你确实卡在某个知识盲区可以向面试官坦诚“这块我之前接触得少但我可以讲讲和它相关的 XXX 部分。”把话题牵引到自己的优势领域展示学习能力通常能挽回不少印象分。5.3 Kotlin 和 Dart 双写时的“语言细节加分项”由于你面的是 Android 和 Flutter 双端岗位面试官大概率会让你用 Kotlin 或 Dart 写代码。这里有几个语言层面的细节可以帮你加分Kotlin 版加分写法// 合并两个有序数组使用双指针从后往前 fun merge(nums1: IntArray, m: Int, nums2: IntArray, n: Int) { var p1 m - 1 var p2 n - 1 var p m n - 1 while (p2 0) { nums1[p--] if (p1 0 nums1[p1] nums2[p2]) nums1[p1--] else nums2[p2--] } }写完后可以主动说“这段代码用了一个表达式函数体的写法同时把多个指针的更新压缩到一行如果团队规范要求可读性优先我可以改成更展开的写法。”这会让面试官觉得你既懂 Kotlin 的语法糖又了解代码规范。Dart 版加分写法// 判断字符串是否是回文串忽略非字母数字字符 bool isPalindrome(String s) { int left 0, right s.length - 1; while (left right) { while (left right !_isAlphanumeric(s.codeUnitAt(left))) left; while (left right !_isAlphanumeric(s.codeUnitAt(right))) right--; if (_toLower(s.codeUnitAt(left)) ! _toLower(s.codeUnitAt(right))) return false; left; right--; } return true; }写 Dart 时主动提到“String.codeUnitAt返回的是 UTF-16 码元常规字符没问题但 emoji 这类增补平面字符需要处理代理对”面试官会立刻对你的工程细节能力留下印象。6. 高频题“记忆复盘”清单刷题不在多在精6.1 我建议优先刷透的 20 道题与其刷 300 道题然后全忘光不如把 20 道核心题反复吃透。下面这个清单是我结合自己和身边同事的面试经历整理出来的覆盖了所有高频考察类型序号题目核心考点举一反三1两数之和哈希表三数之和、四数之和2三数之和排序 双指针去重思路3合并两个有序数组逆向双指针合并两个有序链表4反转链表三个指针迭代/递归K 个一组反转5环形链表 II快慢指针 数学推导找重复数6最长无重复子串滑动窗口 哈希表最小覆盖子串7二叉树的层序遍历队列 BFS锯齿形遍历8最大深度/最小深度递归/迭代 DFS平衡二叉树9最近公共祖先递归回溯BST 的 LCA10快速排序partition 分治数组第 K 大11手写 LRU哈希表 双向链表LinkedHashMap 版12爬楼梯动态规划最小花费爬楼梯13打家劫舍动态规划环形数组版本14零钱兑换BFS/动态规划排列数版本15KMP next 数组前缀后缀最大匹配字符串匹配16编辑距离二维 DP最长公共子序列17岛屿数量DFS/BFS 染色矩阵中的连通分量18合并区间排序 扫描插入区间19有效括号栈括号生成、最长有效括号20寻找两个正序数组的中位数二分 分治第 K 小元素这个清单中的题目我每道都写了题解笔记重点记录“为什么这样做”而不是“这样做是什么”。笔记的形式可以是三行题目、关键思路、常见坑。面试前把笔记过一遍比临时刷新题效果好得多。6.2 每道题刷三遍才算真正“过”我自己的刷题节奏是“一道题刷三遍”。第一遍不看答案先自己写卡住超过 30 分钟再去看题解看完题解后关掉题解自己重新写一遍第二遍在 3 天后不参考任何笔记凭记忆手写写不出来的地方重点标记第三遍在面试前一周用“讲解”的方式把题的思路完整说一遍想象对面坐着面试官。第三遍特别重要也是很多人忽略的一步。能够把一道算法题讲清楚才证明你真正理解了它。讲不清楚的地方就是你理解的盲区。我在准备“环形链表 II”这道题时第一遍会做第二遍会写第三遍想跟朋友讲“为什么快指针追上慢指针后从链表头再走一个指针就能找到环入口”却发现根本讲不清。后来我画图推导了一遍数学关系才彻底弄明白。这个过程中的收获比做十道新题都要大。7. 我踩过的算法面试“致命坑”实录7.1 坑一过度追求最优解基础解法全忘我早期面试踩过最大的坑是一道题还没开始写满脑子想的是最优解。有一道“找到数组中出现次数超过一半的数字”摩尔投票法我第一反应是用哈希表统计次数哈希表当然也能做但面试官追问“能不能 O(1) 空间”我当时卡住了。后来复盘发现我连“排序后中间位置元素就是众数”这个朴素思路都没想到因为我一门心思只想怎么秀最优解。建议在面试中先把最简单、最不容易出错的解法写出来并跑通再主动讨论优化方案。这比你一上来就写一个复杂解法然后卡在边界条件上好得多。面试官要的是能交付的代码而不是炫技的代码。7.2 坑二只刷题不复习面试时“似曾相识却写不出来”这个问题在加班多、时间散的 Android/Flutter 开发中特别常见。你会觉得“这题我刷过”可真让你在面试现场写却卡在中间某一步。原因就是刷题时你太依赖题解缺少“隔断记忆”的提取练习。我后来养成了一个习惯手机上建了一个“算法题 No 复盘”的相册每刷一道题就用手机录一段 2 分钟的视频自己对着镜头讲思路。每周快速刷一遍这些视频回忆对应的解法。这个过程比反复做新题高效得多因为你在激活记忆而不是重新学习。7.3 坑三忽略与面试官互动沉浸在自己的世界里很多技术能力强的人面试时容易进入“一个人写代码”的状态全程不和面试官交流。这会让面试官无法了解你的思路也无法给你提示。如果你写的解法出了问题面试官想引导你修正也会因为没机会插话而放弃。正确的做法是每写一个关键逻辑就简单用一句话说明“这里我用的是双指针两个指针分别从数组头和尾向中间移动直到相遇为止”“这里我把递归终止条件设为root null因为树为空时没有深度”让面试官始终跟得上你的思路。如果写错了面试官也能及时纠正避免你在错误方向越走越远。8. 现场提问环节的反问技巧把“算法面试”变成“技术交流”面试接近尾声时面试官通常会问“你有什么要问我的吗”这个环节大多数人都在纠结“项目组用的什么技术栈”“加班多不多”其实你完全可以借这个机会把算法话题延续下去。我更推荐这样问“贵司的客户端团队在做性能优化时对列表渲染有没有沉淀自己的算法策略或框架比如有没有类似自研的 diff 算法或者分级缓存方案”这个问题既表达了你对算法的热爱又展示了你对客户端性能优化的思考比单纯问“加班多不多”有质量得多。如果对面是 Flutter 方向的面试官可以追问“你们在 Flutter 上做长列表时遇到卡顿一般是先考虑RepaintBoundary优化还是先考虑数据层分页”这种问题能引出面试官自己的经验他会更愿意跟你多聊一会儿聊得越久你通过的概率就越大。9. 最后的真心话算法面经准备到什么程度才算“够”准备算法面试这件事一定要和自己较真但不要自我折磨。我看到过很多人刷题刷到深夜第二天精神萎靡地去面试状态全无。我的经验是算法面试准备是长期功夫不是考前突击能解决的。每天保持 1-2 道题的节奏坚持三个月比考前一周每天刷 20 道题有效得多。在 Android 和 Flutter 双端开发的日常工作中算法能力会以非常隐蔽的方式呈现。比如你写的DatabaseRepository里对分页数据的缓存是否够高效比如你的Flutter ListView是否在大数据量下出现了掉帧又比如你在做MethodChannel批量传输大数据时是否需要考虑压缩编码。这些问题的底层能力都指向算法素养。根据我个人的经验算法面试最终考察的是“代码味”和“逻辑感”。你不需要做题库里所有的题但你需要理解数据结构的本质、掌握复杂度分析的直觉、养成先思考再编码的习惯。面经只是地图真正走完全程的还是你自己。希望这篇整理能帮你少走一些弯路愿你在准备的过程中能在算法和工程之间找到属于自己的那个平衡点。