稳解是转为求补集:用HashSet做集合差或BitSet标记存在性;HashSet适合范围明确、内存不敏感场景,构造全集后用removeAll剔除List元素,禁用TreeSet避免多余排序开销。

Java里List找缺失数字,别先想排序和循环
直接遍历对比或排序后查空隙,代码写起来快,但一遇到重复元素、负数、超大范围就崩。真正稳的思路是:把问题转成「已知全集,求补集」——要么用Set做集合差,要么用位图(BitSet)标记存在性。
用HashSet做差集最省心,但要注意边界
适合已知数字范围明确(比如1到n)、且内存不敏感的场景。核心是构造一个完整集合,再用removeAll()剔除List里的元素。
-
Set<integer></integer>必须用HashSet或LinkedHashSet,别用TreeSet——它会自动排序,但你不需要,还多花O(n log n) - 构造全集时别硬写
for (int i = 1; i ,先用<code>Collections.max()和Collections.min()确认实际范围,否则List里缺的是1000以外的数,你只扫到100,永远找不到 - 如果List含重复值,
removeAll()不受影响;但若List为空或全是非法值(如null),得提前判空,否则NullPointerException
示例:Set<integer> full = IntStream.rangeClosed(1, max).boxed().collect(Collectors.toSet());</integer>,再full.removeAll(list);
超大范围(比如1~2^31)只能靠BitSet,但别乱new
BitSet省内存、查得快,但它下标从0开始,且不支持负数。如果你要查1~1000000之间的缺失值,new BitSet(1000001)才对,不是1000000——否则set(1000000)越界。
立即学习“Java免费学习笔记(深入)”;
- 务必先过滤掉范围外的数,
list.stream().filter(x -> x >= 1 && x ,不然<code>set()抛IndexOutOfBoundsException -
BitSet.length()返回的是最高位+1,不是实际容量;用nextClearBit(1)找第一个缺失值时,得从1开始,不是0(除非0也在你的目标范围内) - 如果缺失多个数,别用
nextClearBit()反复调用——它内部是线性扫描,大数据量下慢;改用stream().mapToInt(...)配合BitSet.stream()(Java 8+)更稳
别忽略原始数据类型和装箱开销
如果List是List<integer></integer>,但原始数据来自int[]或数据库整型字段,中间多一次自动装箱。高频调用或大数据量下,这会让GC压力明显上升。
- 能用
IntStream就别用Stream<integer></integer>,例如Arrays.stream(arr).boxed().collect(Collectors.toList())这种转换尽量前置,别在查找逻辑里做 - 如果List本身是
ArrayList且已排序,别急着转Set——用双指针扫一遍反而O(n),比建哈希表还快,尤其当缺失数极少时 - 测试时别只用{1,2,4}这种小样例,加个
Integer.MAX_VALUE - 10进去,看看BitSet是否爆内存,HashSet是否触发扩容抖动
最麻烦的不是算法选哪个,而是没想清楚「缺失」到底指什么:是连续区间里的空档?是某个预设数组的补集?还是两个List之间的差?定不准这个,后面全白搭。










