你在公司赶着上线一个文件同步工具,用户一上传文件,系统就得快速记录、归类、查找。这时候,光会用数组、链表还不够,得知道怎么设计合适的数据结构。这就像整理家里衣柜——衣服堆成山时,随便塞肯定不行,得分季节、类型、使用频率,还得能快速找到。
一个真实的场景:增量备份怎么存?
假设你要实现一个增量备份系统,每次只保存变化的文件块。这些块有唯一的ID,还要支持快速查询是否已存在、合并相邻块、按时间排序回放。如果直接用列表一条条扫,数据一大,查一次就得几秒,谁等得起?
这时候,你会想到哈希表存ID做去重,用跳表或平衡树维护时间顺序。把两个结构联动起来,查得快,插得也稳。这不是理论题,是每天备份系统都在处理的事。
实战练习题1:设计一个带版本的键值存储
要求:支持 set(key, value, timestamp),get(key, timestamp) 能查到该时间前最新的值。比如你备份了代码文件的多个版本,想回退到昨天下午三点的状态。
别急着写HashMap嵌套List。想清楚时间戳是递增的,那每个key对应的value历史可以用数组存,查的时候二分搜索。这样插入O(1),查询O(log n),比遍历快多了。
class VersionedKV {
Map<String, List<Pair<Integer, String>>> data;
void set(String key, String value, int time) {
data.computeIfAbsent(key, k -> new ArrayList<>()).add(new Pair<>(time, value));
}
String get(String key, int time) {
List<Pair<Integer, String>> history = data.get(key);
if (history == null) return null;
// 二分查找最后一个 <= time 的项
int lo = 0, hi = history.size() - 1;
while (lo < hi) {
int mid = (lo + hi + 1) / 2;
if (history.get(mid).time <= time) lo = mid;
else hi = mid - 1;
}
return history.get(lo).time <= time ? history.get(lo).value : null;
}
}
实战练习题2:设计一个可撤销的文件操作日志
用户每删一个文件,系统记一笔。要支持undo,也就是回滚上一步。这听起来像栈——后进先出,push操作,pop撤销。
但如果要支持撤销多次,还能redo,就得加个“反向栈”。执行操作时压入主栈,撤销时弹出并压入备用栈。redo就反过来。两个栈来回倒腾,像是在时光机里前进后退。
实战练习题3:高效合并备份区间
你的备份不是连续做的,可能早上8:00-8:15,再8:10-8:20,这两个时间段重叠了。怎么合并成一个8:00-8:20的大区间?
这本质是“合并区间”问题。先把所有区间按起始时间排序,然后逐个合并。当前区间的结束时间大于下一个的开始时间,就更新结束时间;否则断开,新开一段。这种结构在日志压缩、空间回收里特别常见。
数据结构不是背概念,是在具体问题里权衡取舍。内存紧就省空间,查询多就换时间。练这些题,不是为了过面试,而是当你坐在电脑前,脑子里能蹦出合适的模型,而不是只会for循环硬扫。