上篇聊完架构设计,这篇回到算法。美团算法面试中等偏上,Medium题要求思路清晰、代码无Bug、复杂度分析到位。Android岗集中在树、栈/队列、贪心和字符串匹配,不考图论和高级DP,但手写代码要能跑通。
今天8道题覆盖美团算法核心考点,每道给出思路+代码+复杂度。
Q1:二叉树的前中后序遍历?迭代怎么写?
递归写法简单:前序(根→左→右)、中序(左→根→右)、后序(左→右→根)。三行代码搞定。
迭代写法(面试常考):用栈模拟递归。
前序迭代:根入栈,循环弹栈处理 → 右子入栈 → 左子入栈(栈后进先出,左子先处理)。
List<Integer> preorder(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root != null) stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
return result;
}
中序迭代:一路向左入栈,到头弹栈处理,转向右子树。
追问:中序遍历BST结果有序(从小到大),验证BST只需中序遍历看是否递增。BST查找O(log n),退化链表时O(n)。
Q2:层序遍历怎么按层输出?
用队列BFS。关键:每层处理前先记录队列当前size,循环size次弹出处理,这样就知道哪些节点在同一层。
List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root != null) queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> level = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(level);
}
return result;
}
追问:二叉树最大深度怎么求?BFS层序遍历的层数就是最大深度。或递归:maxDepth = 1 + max(maxDepth(left), maxDepth(right))。时间O(n)空间O(h)。
Q3:用栈实现队列?用队列实现栈?
栈实现队列:两个栈。stack1用于push,stack2用于pop/peek。pop时如果stack2为空,把stack1全部倒入stack2(顺序反转),再从stack2弹出。均摊O(1)。
class MyQueue {
Stack<Integer> s1 = new Stack<>(), s2 = new Stack<>();
void push(int x) { s1.push(x); }
int pop() {
if (s2.isEmpty()) while (!s1.isEmpty()) s2.push(s1.pop());
return s2.pop();
}
}
队列实现栈:两个队列。push加入q1,pop时把q1前n-1个元素倒入q2,弹出q1末个元素,交换q1和q2。pop是O(n)。
追问:Android里栈的用途?Activity返回栈、Fragment addToBackStack、方法调用栈。队列呢?Handler的MessageQueue、线程池BlockingQueue。
Q4:最小栈怎么实现?
支持push/pop/top/getMin都是O(1)的栈。
方案:两个栈。data栈存数据,min栈存当前最小值。push时data正常push,min栈push(min(新值, min栈顶))。pop时两个栈同时pop。getMin返回min栈顶。
class MinStack {
Stack<Integer> data = new Stack<>(), min = new Stack<>();
void push(int x) {
data.push(x);
min.push(min.isEmpty() ? x : Math.min(x, min.peek()));
}
void pop() { data.pop(); min.pop(); }
int top() { return data.peek(); }
int getMin() { return min.peek(); }
}
追问:能用一个栈吗?可以——每个元素存value和当前min的pair,或存差值(当前值-min),空间更省但注意溢出。
Q5:贪心算法——区间调度问题
问题:给一组区间[start, end],选最多互不重叠的区间。
贪心策略:按结束时间排序,依次选不重叠区间。每次选结束最早的给后面留最多空间。
int maxIntervals(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
int count = 1, end = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] >= end) { count++; end = intervals[i][1]; }
}
return count;
}
时间O(n log n)排序,空间O(1)。
追问:贪心正确性——选结束最早的区间不影响后面可选区间。最优解没选结束最早的可以替换,解不会变差。
Q6:字符串匹配——KMP算法
暴力匹配:O(m*n)。文本串每个位置跟模式串逐字符比较,不匹配回退。
KMP:O(m+n)。next数组(前缀表)记录模式串每个位置的最长相等前后缀长度。匹配失败时不回退文本串指针,根据next数组跳过已知匹配的前缀。
next数组构建:双指针i遍历模式串,j指向当前最长前缀末尾。next[i] = pattern[0..i]的最长相等前后缀长度。
Android关联:Java的String.indexOf用朴素匹配(短字符串够用),大文本搜索用KMP或Boyer-Moore。
追问:Rabin-Karp用滚动哈希——hash相等才逐字符比较,平均O(n)最坏O(mn)。适合多模式匹配场景。
Q7:字符串反转和回文判断
反转字符串:双指针头尾交换。O(n)时间O(1)空间。
判断回文:双指针从两端向中间比较,跳过非字母数字字符,忽略大小写。遇到不匹配返回false,全部匹配返回true。O(n)时间O(1)空间。
追问:最长回文子串?中心扩展法——每个字符和字符间作为中心向两边扩展,记录最长。O(n²)时间O(1)空间。Manacher O(n)面试一般不考。
Q8:算法面试怎么准备?
刷题顺序:按类型——树→栈/队列→贪心→字符串→DP→二分。每类型10-15题。先Easy打基础再Medium练速度。Hard看时间,Android岗不强制。
面试技巧:确认题意 → 讲思路(不闷头写)→ 边写边讲 → 手动跑测试 → 分析复杂度。
常见错误:忘边界条件(空输入/单元素)、变量名太短易混、不测试就说没问题。
面试Tips:美团算法二叉树考前中后序迭代写法和层序BFS。栈/队列考互相实现和最小栈。贪心考区间调度(按结束时间排序)。字符串考KMP思路和回文判断。准备美团算法刷LeetCode Hot 100的树/栈/贪心/字符串分类就够。面试时讲思路比闷头写更重要——面试官想看你的思考过程。
下一篇进入Kotlin专项——协程异常处理、Channel与Flow、Kotlin编译器插件、KSP。
面试被考过KMP的同学评论区举个手,你能手写next数组吗?
本系列连载中,关注不迷路,下一篇:美团中级Android(Kotlin专项)面试真题
系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。