P8架构师聊完系统设计,这篇回到P5级别的数据结构与算法。算法是阿里面试第一关——P5必须过算法,面试官给一道LeetCode Medium,20分钟写完跑通,过不了后面都不用聊。
Android岗算法题集中在排序、链表、树、动态规划和缓存设计,难度Medium居多,要求讲清时间空间复杂度。
今天8道题覆盖P5算法核心考点,每道给出思路+代码+复杂度分析。
Q1:常见排序算法对比?Android开发用哪些?
快速排序:平均O(n log n),最坏O(n²)。选随机pivot避免最坏。空间O(log n)递归栈,不稳定。
归并排序:稳定O(n log n),空间O(n)。适合链表排序。
堆排序:O(n log n),空间O(1),不稳定。适合TopK问题。
Android实际:Collections.sort用TimSort(归并+插入混合),Arrays.sort用DualPivotQuickSort。面试考手写一般考快排和归并。
追问:快排为什么比归并常用?常数因子小——快排in-place不需要额外数组,实际运行比归并快2-3倍。但归并稳定(相等元素不交换位置),排序自定义对象时用归并更可靠。
Q2:链表反转怎么实现?迭代和递归都写一下
迭代法:三个指针prev/curr/next,遍历链表逐个反转next指向。时间O(n)空间O(1)。
ListNode reverse(ListNode head) {
ListNode prev = null, curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
递归法:递归到末尾,回溯时反转next指向。时间O(n)空间O(n)递归栈。
追问:反转链表前N个节点怎么做?递归加计数器,到第N个停止反转,把第N+1节点接到反转后尾部。这个变体面试考得多。
Q3:判断链表有环?找环的入口节点?
判断有环:快慢指针。slow每次走一步,fast每次走两步。如果有环,fast一定会追上slow(在环里绕圈)。时间O(n)空间O(1)。
找入口:相遇后,把一个指针放回head,两个都每次走一步,下一次相遇就是环入口。证明:fast走了2倍距离,多走的恰好是环的整数倍,所以从head和相遇点同速走必然在入口相遇。
追问:两个链表相交怎么找交点?先算两链表长度差d,长链表先走d步,然后两个同时走,相遇点就是交点。或者更优雅:A走到末尾跳到B头,B走到末尾跳到A头,两者走的总距离一样,相遇点就是交点。
Q4:二叉树的层序遍历?怎么实现?
用队列实现BFS。每层处理时先记录队列当前大小(当前层节点数),循环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;
}
追问:判断二叉树对称?递归比较左右子树:isMirror(l, r)要求l.val==r.val且l的左子树跟r的右子树镜像。
Q5:动态规划入门?斐波那契怎么优化?
动态规划核心:把大问题拆成子问题,缓存子问题解避免重复计算。关键是找状态转移方程。
斐波那契:F(n) = F(n-1) + F(n-2)。递归O(2^n)大量重复。DP数组O(n)时间O(n)空间。空间优化:两个变量滚动,O(n)时间O(1)空间。
int fib(int n) {
if (n <= 1) return n;
int prev = 0, curr = 1;
for (int i = 2; i <= n; i++) {
int next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
追问:爬楼梯问题(每次1或2阶)跟斐波那契什么关系?完全一样——到第n阶的方法数 = 到第n-1阶 + 到第n-2阶。面试考爬楼梯就是考斐波那契DP。
Q6:背包问题怎么解?0-1背包和完全背包?
0-1背包:N个物品各有重量w和价值v,背包容量W,每个最多选一次。状态转移:dp[j] = max(dp[j], dp[j-w[i]] + v[i]),一维数组逆序遍历。
完全背包:每个物品可选无限次。状态转移一样但正序遍历。
Android关联:内存分配优化可建模为背包——有限空间选加载哪些模块使收益最大。
Q7:LRU缓存怎么实现?
LRU(Least Recently Used)淘汰最久未使用的元素。Android的LruCache就是LRU实现。
数据结构:HashMap + 双向链表。HashMap保证O(1)查找,双向链表维护访问顺序。
get操作:HashMap查找,命中后把节点移到链表头部。O(1)。
put操作:插入/更新,新节点放头部。超过容量删链表尾部(最久未用)并从HashMap移除。O(1)。
class LRUCache {
int capacity;
LinkedHashMap<Integer, Integer> map;
LRUCache(int capacity) {
this.capacity = capacity;
map = new LinkedHashMap<>(capacity, 0.75f, true);
}
int get(int key) {
return map.getOrDefault(key, -1);
}
void put(int key, int value) {
map.put(key, value);
if (map.size() > capacity) {
map.remove(map.keySet().iterator().next());
}
}
}
追问:Java的LinkedHashMap怎么实现LRU?构造时传accessOrder=true,每次get/put会把节点移到链表末尾。重写removeEldestEntry返回size()>capacity就自动淘汰最老的。面试写LRU用LinkedHashMap三行代码搞定。
Q8:Android的LruCache原理?跟LRU缓存什么关系?
LruCache是Android提供的内存缓存工具类,底层就是LinkedHashMap实现LRU策略。
构造参数:maxSize(缓存大小上限,通常用字节数或条目数)。每次put后调trimToSize——如果超过maxSize就移除eldest条目。
典型用法:图片缓存。new LruCache<String, Bitmap>(maxMemory / 8) 缓存Bitmap对象,key是URL。命中直接返回不调网络/磁盘。
追问:LruCache和DiskLruCache区别?LruCache是内存缓存(快但App退出丢失),DiskLruCache是磁盘缓存(慢但持久化)。Glide三级缓存:ActiveResources → LruCache → DiskLruCache → 网络。
面试Tips:P5算法面试排序考快排和归并手写+复杂度分析。链表考反转/判环/相交(快慢指针是核心技巧)。树考层序遍历(BFS队列)。动态规划考斐波那契/爬楼梯/背包。LRU缓存考HashMap+双向链表实现。准备算法面试刷LeetCode Hot 100足够,Android岗不考Hard。
下一篇进入设计模式专项——单例五种写法、观察者模式、策略模式,这些在Android源码里到处都是。
面试被考过LRU手写的同学评论区举个手,你用的LinkedHashMap还是手写双向链表?
本系列连载中,关注不迷路,下一篇:阿里P6中级Android(设计模式专项)面试真题
系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。