选错数据结构,效率大打折扣
很多人写算法时只盯着逻辑能不能跑通,却忽略了数据结构的选择。比如在一个需要频繁查找元素的场景里用了数组,每次查找都要遍历一遍,时间复杂度直接拉满。其实换成哈希表(map 或 set),查找就能从 O(n) 降到接近 O(1)。就像去图书馆找一本书,一本本翻肯定不如查目录来得快。
举个例子:判断数组中是否存在两个数和为 target,用双重循环是常见做法,但稍微改一下,边遍历边存进 map 里查补数,代码简洁还高效。
for (int num : nums) {
int complement = target - num;
if (seen.contains(complement)) {
return true;
}
seen.add(num);
}边界条件处理太随意
空输入、单元素、重复值、溢出……这些细节看着不起眼,但在实际系统中往往是崩溃的源头。比如二分查找,很多人记得“left + right) / 2”,但没考虑整数溢出。更稳妥的写法是 left + (right - left) / 2。
再比如链表操作,删节点时如果不判断前驱是否存在,程序可能当场罢工。现实开发中,一个未处理 null 的接口调用,轻则返回错误,重则服务雪崩。
递归写成“无限套娃”
递归很优雅,但也容易掉进栈溢出的坑。最常见的问题是终止条件写漏或者压根没想清楚。比如斐波那契数列用朴素递归:
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}看着简单,可当 n 到 40 以上时,函数调用次数呈指数增长,响应慢得像卡顿的视频。这时候就得考虑记忆化搜索或改成迭代。
贪心策略不一定最优
看到“每次选最大/最小”就上贪心,这是很多人的直觉。但贪心只在特定条件下成立。比如零钱兑换问题,面额为 [1,3,4],要凑 6,贪心会选 4+1+1,共三枚;而最优解是 3+3,只要两枚。
这种时候就得老老实实用动态规划,把每种金额的最小硬币数都算出来。别指望一步到位,系统软件里大多数复杂问题都得一步步推导。
测试用例覆盖不全
写完算法随手拿个普通例子试一下就提交,结果上线后遇到特殊输入直接出错。建议准备几类典型用例:空输入、极端值、有序/逆序数据、大量重复元素等。
比如排序算法,除了随机数组,还得试试已经排好序的、完全逆序的、所有元素相同的。这些在真实业务中都可能出现,比如用户按时间倒序刷日志,传来的就是逆序数据。
忽视空间换时间的平衡
有时候为了降低时间复杂度,适当增加空间是可以接受的。比如用额外的数组记录状态,避免重复计算。但也要警惕内存占用过高,在嵌入式或高并发场景下,空间资源同样紧张。
像 LRU 缓存这种经典设计,就是用哈希表加双向链表,换取 O(1) 的访问和更新速度。系统软件中这类权衡无处不在,不能只看单一指标。