惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
SegmentFault 最新的问题
博客园 - 聂微东
罗磊的独立博客
W
WeLiveSecurity
博客园_首页
Scott Helme
Scott Helme
V
Visual Studio Blog
T
The Exploit Database - CXSecurity.com
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
Latest news
Latest news
L
Lohrmann on Cybersecurity
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
About on SuperTechFans
F
Full Disclosure
Y
Y Combinator Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 司徒正美
博客园 - Franky
C
CXSECURITY Database RSS Feed - CXSecurity.com
F
Fortinet All Blogs
Blog — PlanetScale
Blog — PlanetScale
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
阮一峰的网络日志
阮一峰的网络日志
S
Schneier on Security
雷峰网
雷峰网
博客园 - 【当耐特】
P
Privacy International News Feed
C
Cyber Attacks, Cyber Crime and Cyber Security
Engineering at Meta
Engineering at Meta
aimingoo的专栏
aimingoo的专栏
MongoDB | Blog
MongoDB | Blog
J
Java Code Geeks
T
Tor Project blog
V
V2EX
爱范儿
爱范儿
C
Check Point Blog
T
Threatpost
Project Zero
Project Zero
量子位
V
Vulnerabilities – Threatpost
Know Your Adversary
Know Your Adversary
I
Intezer
G
GRAHAM CLULEY
P
Privacy & Cybersecurity Law Blog
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

推广

Codex 助力小项目,全部重写了一遍,有点意思! 管转满血模型 GPT-5.5 / gpt-image-2 API 已上线,倍率调低 0.3,注册留 ID 送 5 美金额度 GPT-5.5 / gpt-image-2 API 已上线, 0.06$/张,注册留 ID 送 5 美金额度 不知道为什么, V2EX 的铜币实在是用不完。今天也没什么特别要宣传的,就随便来发发牢骚,消费一下铜币吧。 腾讯云国际站代充值渠道-支持 USDT 支付-免绑卡注册——LingduCloud 零度云! [发 15 个码] 开发的 Mac 应用 Deepseek coding plan 限量供应 腾讯云国际企业认证账号出售:阿里云/华为云国际可开国内节点 codex 中转,留邮箱就送。 5.1 香港旅游, Za bank 众安银行开户额外返现 300HKD 目前 GPT Plus 价格已经顶上天了,我们开发了 Pro 200 刀 拼车功能.... 3.3 元 即可上车 GPT5.5 出了? 限时 0.1x 倍率 送刀了 评论区抽 10 刀共*10 阿里云国际站充值代充-官方授权分销商开户——LingduCloud 零度云 [福利] 分享一个完美解锁 GPT-5.4 限制的住宅代理: Novproxy(内附 500M 免费试用) GPT 5.5 上了 0.1x 还不来? SentiCat 开启公测:一款自研架构的桌面 Agent,主打“任务执行+情感陪伴” 自用 OpenAI API 中转 🤡 自己搞了个 AI token 中转,能用 Codex / Claude Code,随便测下 Ai2You 智友社-让更多的人用上更好的 Ai,解决您的 AI 焦虑! 一个英语学习 APP [iOS] ,送 50 个终身会员 [中转] A 社官方满血 Opus-4-7 上线,限时免费开蹬!支持对接 & 开票! aicoding.sh: Claude API 企业级中转服务,数万用户稳定运行 GPT-5.5 上线 0.2x 倍率 Codex Plus/Pro 高缓存号池 持续稳定一个月 5.4 典型用量 8 元一亿 Token 新开业中转站 kir 0.2x GTP 0.1x gpt-image-2 0.08 华为云实名号购买:阿里云|腾讯云实名号免费开 感谢 V2EX 上各位 NAS🍆 和 Datahoarder 玩家的关注和真实反馈!作为个人开发者,能得到这么多硬核玩家们的讨论,我非常荣幸。 今天摸鱼给 NanaAI 也接入了 GPT-Image-2 腾讯云国际站实名账号 LingduCloud 零度云:腾讯云国际站实名账号认证教程! 价格差不多是官方 8 分之一的 codex 渠道,分享给大家 [星问] 紫微斗数 AI 智能解盘送福利了 期权经验分享&晒单 🌍 找稳定代理看这里! Novproxy 动态住宅 IP:注册免费领 500M,全球 200+ 国家覆盖 🛰️ [www.DDShub.cc] 呆呆兽中转站,满血 Claude/CodeX [AI 中转站] 球球 Token - AI 聚合中转服务站 拉新活动 已支持 CodeX、Claude Code 华为云国际站实名账号代充值 LingduCloud 零度云:华为云国际站 ECS 服务器使用教程! 中转站正式试营 欢迎试用 不掺不假 老牌号商不跑路 再来安利一个中年男人的兴趣论坛 M 站, 风格有点像大疆/理想? 功能也很有趣, 总归是能多一个 ~~摸鱼~~ 交友的地方 [岁岁-极简农历日程] 没找到好用的农历日程提醒 App,所以我自己撸了一个:无广告、支持系统日历日程写入 做了一个自己的 AI 中转站,球球 Token,现支持 CodeX 和 Claude Code 麋鹿云送福利啦 [Codex 中转站]不跑路的老王送福利了,新用户即送 10$ 🔥Racknerd 全新套餐上架(涨价了,机房选择也变多了) 做了一个 GPT Image 2 生成器,不得不说 GPT Image 2 生成的美女真漂亮 $10 FREE| 用 Claude Code 写代码,付费只需官方价 1.9 折 这些图是 GPT-Image-2 出的?效果炸裂!可以来我的 gpt-image-2.me 网站体验 中转站正式试营 欢迎试用 不掺不假 claude code / codex 免费 API 来了 [Codex 中转站]HyperAPI 又来送福利了,新用户注册即送 5$, 评论留下邮箱送 5$兑换卷 华为云国际站实名账号代理商 LingduCloud 零度云:华为云国际站实名账号认证教程! 新机器人开 team 这个权威的中转站现在有活动,注册就能领体验金 为什么我选择在这里打破沉默?一个独立开发者的自白:从 NAS 积灰到“零解压秒开”的终极救赎 (附日文原稿) 🚀 招募出海合伙人:我出技术和流量,你来赚差价! 200G 客户测试份额免费领 可以免费白嫖 GPT Image 2 的网站,每天都有刷新额度 V2EX › 登录 从零开始,在国内网络跑通 Claude Code 等一切 Agent 框架 小龙虾和爱马仕已经 out 了 AI 最强 Agent 小龙猫才是 YYDS lingducloud:海外云服务器购买避坑指南 [中转站推广] AIHub2API 中转站送福利,本站新用户送 6 刀,每 10 楼抽 1 张 100 刀日卡 Kookeey 动态住宅 IP:全网底价,支持动态免费测试 国际阿里云实名账号代理商 LingduCloud 零度云: 2026 最新阿里云国际站实名账号注册教程! 平台的特价稳定渠道 GPT Pro 系列马上绝版,需要的 NS 兄弟别错过 [公测] APIBox 中转站推广送福利💥 Claude Code 被封号无数?防封号终极指南,字字千金, 12 条硬核规则助你构建'正常人类行为档案'! - V2EX [薅羊毛] 纯净住宅代理 IP 免费领, Novproxy 新人专属 CDK,手慢无! [福利/AI 中转站]OneXModel 新注册送 10$体验金,官方纯血 claude 最低 2.2 折!极致性价比! $10 FREE | Claude API 官方价一折起,国内直连无需折腾 火山方舟的 coding plan 上线新模型了,支持最新开源的 glm 5.1, kimi 2.6 和 minimax-m2.7 Claude Pro 不能用 Claude Code? 今天把 GPT Image 2 接进来了,做了个小站可以免费体验 2 次 [福利] 给各位 大佬薅个 1G 动态住宅 IP 羊毛,先测再玩! 网易爆米花目前算免费里体验最好的了吧,别和 infuse 比 - V2EX 满血 Claude opus4.7 和 Codex Pro 5.4 美国家宽池,不降智!一个越用越稳的 API 中转站,回帖送美元额度! 不只是 NAS:走出局域网, Nas Player Pro 依然能打 ✨都在说 VMRack 好,我来看看怎么个事 | 测评 L1 L2 L3 三条线路,果然 L3 机器确实很赞,没什么库存,购买靠抢(文末有抢机器的方法) [福利] Mirrorstages 今天免费了! GPT-5.4 随便蹬,顺便聊聊最近 Plus 的漏洞 这种小程序开发报价多少钱合适?给甲方报价 3000 会不会过分 - V2EX Kookeey 动态住宅 IP:全球 180+ 国家,城市定位,支持动态免费测试 🚀 Unity2.Ai 公测招募|直达源头,拒绝降智, aws 企业渠道,新用户送 2 刀加群送 10 刀免费体验 做了一个短信平台(支持全球 OTP / 营销),想听听大家的意见 LiteLLM 接 AWS Bedrock 做了个 Claude API 平台,现在找种子用户, V2EX 用户专属 100 元无门槛优惠券 🚀 Unity2.Ai 公测招募|直达源头,拒绝降智,新用户加群送 10 刀免费体验 大厂国际云 [腾讯、阿里、AWS] 开户充值,长期续费 - V2EX # 抽奖 | 懒猫小龙猫 AI Agent 抽奖活动(第一轮) 落魄前端失业一年多决定帮岳父卖五常大米 国际站阿里云代理商实名企业账号 LingduCloud 零度云,做海外业务这路子其实挺省心! 免实名高速稳定海外服务器 即开即用 单一中转站不稳定,试试这个 1openapi.com - V2EX 既然推广节点全是中转站,我也来发点测试额度 - V2EX 便宜服务器真的能用吗?我用过之后的真实感受 服务器 seo 站群 多 IP 搭建 少 IP 物理机 稳定 诚信 靠谱 - V2EX macOS 上班摸鱼神器 Glanceway 更新(二) - V2EX 十年机房经验 💥 [最高领 $10 测试金] 纯血 Codex Pro 池 + 高智商 Claude!一个越用越稳的 API 中转站 Claude API 官方价一折起,企业级直连稳定接入 “万 1 免 5” a 股券商低佣免五开户,抽一箱汾酒 [6 瓶] ~~ [大笑脸开户] 香港排名第一数字银行 众安银行开户额外返现 300HKD . - V2EX Bitget 钱包,稳定支付 AI 的 U 卡,首笔返现 50%, 结束 4 月 30 号,新用户成功开卡返 3U 香港排名第一数字银行 众安银行开户额外返现 300HKD ! - V2EX [免费领取 1G 测试流量包] 动态住宅 IP | 住宅代理 | 海外 IP | ResidentialIP
Java 数组去重的 20 种实现方式,用不同思路解决问题,更好地指导 AI 编程
jarryli · 2026-05-08 · via 推广

数组与列表去重是最常见的算法。看似简单,但不同实现方式的性能差异可能高达几百倍。整理 Java 数组去重的 20 种写法,按 5 个策略分类,帮你理解每类的核心思路。AI 时代,可以不写代码,但需要理解不同解决问题的方式。

第 1 类:基础循环(方法 1-6 )

策略原理:不依赖任何集合工具类,纯靠数组下标、嵌套循环、indexOf 这种"原始"手段来完成去重。每一步判断都是 O(n),整体复杂度是 O(n²)。

// 方法 1:双循环索引比较——当前项跟左侧逐个比较
static int[] unique1(int[] arr) {
    int[] newArr = new int[arr.length];
    int x = 0;
    // 当前项跟左侧全量比较,是否首次出现
    for (int i = 0; i < arr.length; i++) {
        for (int j = 0; j <= i; j++) {
          // i 与左侧每个 j 比对
            if (arr[i] == arr[j]) {
                // 值和位置均相同,表示前面没有相同值,是首次出现
                if (i == j) {
                    newArr[x++] = arr[i];
                }
                break;
            }
        }
    }
    return Arrays.copyOf(newArr, x);
}

// 方法 2:List.indexOf 索引法,跟第一种原理一致
// indexOf 返回首次出现的下标,等于当前下标即首次出现
static Integer[] unique2(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    List<Integer> result = new ArrayList<>();
    for (int i = 0; i < list.size(); i++) {
        // indexOf 得到下标,比较是否跟当前下标一致,如果一致则表示首次出现
        if (list.indexOf(list.get(i)) == i) {
            result.add(list.get(i));
        }
    }
    return result.toArray(new Integer[0]);
}

// 方法 3:从后往前原地删除
// 倒序遍历,与左侧任意相同则删除自身
// 倒序删除的好处:删除点之后的元素都已处理,不会影响下标
static Integer[] unique3(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    int l = list.size();
    // 自后往前遍历原数组
    while (l-- > 0) {
        int i = l;
        // 当前项与左侧逐个比较是否存在重复项,若存在则删除自身
        while (i-- > 0) {
            if (list.get(l).equals(list.get(i))) {
                list.remove(l);
                break;
            }
        }
    }
    return list.toArray(new Integer[0]);
}

// 方法 4:从前往后原地删除(删除后面相同项),与方法 3 相同
static Integer[] unique4(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    int l = list.size();
    // 自前往后遍历原数组
    for (int i = 0; i < l; i++) {
        // 当前项与右侧全部项逐个比较,若有相同则删除相同项
        for (int j = i + 1; j < l; j++) {
            if (list.get(i).equals(list.get(j))) {
                list.remove(j);
                // 删除后 j 位置的元素是原来的 j+1 ,j 不前进
                // l 同步减一
                j--;
                l--;
            }
        }
    }
    return list.toArray(new Integer[0]);
}

// 方法 5:Iterator 遍历,原理与方法 1 同
// 用 Iterator 风格遍历,结果列表用 contains 判重
static Integer[] unique5(Integer[] arr) {
    List<Integer> source = Arrays.asList(arr);
    List<Integer> result = new ArrayList<>();
    Iterator<Integer> it = source.iterator();
    // 逐个迭代,判断是否包含在新数组中
    while (it.hasNext()) {
        Integer item = it.next();
        // ArrayList.contains 是 O(n),整体仍是 O(n²)
        if (!result.contains(item)) {
            result.add(item);
        }
    }
    return result.toArray(new Integer[0]);
}

// 方法 6:从右往左跳过重复,不删除(使用 break 跳出)
// 倒序扫描,若当前元素在左侧已存在则跳过,否则保留
static Integer[] unique6(Integer[] arr) {
    int len = arr.length;
    Integer[] result = new Integer[len];
    int x = len;
    // 自后往前遍历数组
    for (int i = len - 1; i >= 0; i--) {
        boolean duplicate = false;
        // 检查左侧是否有相同元素
        for (int j = i - 1; j >= 0; j--) {
            if (arr[i].equals(arr[j])) {
                duplicate = true;
                break;   // 找到重复立即退出内层循环
            }
        }
        // 没有重复则保留当前元素,倒序填入结果
        if (!duplicate) {
            result[--x] = arr[i];
        }
    }
    return Arrays.copyOfRange(result, x, len);
}

第 2 类:集合容器(方法 7-11 )

策略原理:Java 集合框架本身就提供了"键唯一"的语义。把数据塞进 Set 或 Map ,去重就自然完成。

  • HashSet / HashMap:哈希结构,O(1) 判重,结果无序
  • LinkedHashSet:哈希 + 双向链表,保留插入顺序
  • TreeSet:红黑树,O(logn),自动排序
  • LinkedHashMap:保序的 Map ,能在去重时携带额外信息
// 方法 7:HashSet——最快,但结果无序
static Integer[] unique7(Integer[] arr) {
    // HashSet 自动去重,但底层哈希散列后顺序不可预测
    Set<Integer> set = new HashSet<>(Arrays.asList(arr));
    return set.toArray(new Integer[0]);
}

// 方法 8:LinkedHashSet——保序,工程首选
static Integer[] unique8(Integer[] arr) {
    // LinkedHashSet 内部用双向链表维护插入顺序
    Set<Integer> set = new LinkedHashSet<>(Arrays.asList(arr));
    return set.toArray(new Integer[0]);
}

// 方法 9:TreeSet——自动排序
static Integer[] unique9(Integer[] arr) {
    // TreeSet 基于红黑树,插入即有序(默认升序)
    // 需要降序就用 .descendingSet() 或自定义 Comparator
    Set<Integer> set = new TreeSet<>(Arrays.asList(arr));
    return set.toArray(new Integer[0]);
}

// 方法 10:HashMap 显式判重
// 等价于方法 7 ,但更便于扩展(值可以放统计信息、首次位置等)
static Integer[] unique10(Integer[] arr) {
    Map<Integer, Integer> map = new HashMap<>();
    List<Integer> result = new ArrayList<>();
    for (Integer item : arr) {
        // putIfAbsent 内部判 containsKey + put ,等价但更紧凑
        if (map.putIfAbsent(item, item) == null) {
            result.add(item);
        }
    }
    return result.toArray(new Integer[0]);
}

// 方法 11:LinkedHashMap——保序的 Map
// 适合"按某字段去重并携带其他信息"的场景
static Integer[] unique11(Integer[] arr) {
    // 用 LinkedHashMap 保留插入顺序,键去重,值可携带统计
    Map<Integer, Integer> map = new LinkedHashMap<>();
    for (Integer item : arr) {
        // merge:键不存在则放 1 ,存在则累加(这里相当于做了频次统计)
        map.merge(item, 1, Integer::sum);
    }
    return map.keySet().toArray(new Integer[0]);
}

第 3 类:排序后去重(方法 12-14 )

策略原理:先 sort 让相同元素相邻,再扫一遍删除相邻相同项。复杂度由排序决定,O(nlogn)。优点是不需要额外的哈希结构,"相邻判等"是最便宜的判重方式;缺点是会破坏原顺序,且要求元素可比较(实现 Comparable 或提供 Comparator)。

// 方法 12:排序后从后往前删
static Integer[] unique12(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    Collections.sort(list); // 升序,相同元素聚到一起
    // 倒序扫描,自后往前:相邻两项相同就删掉后一项
    for (int l = list.size() - 1; l > 0; l--) {
        if (list.get(l).equals(list.get(l - 1))) {
            list.remove(l);
        }
    }
    return list.toArray(new Integer[0]);
}

// 方法 13:排序后从前往后删
static Integer[] unique13(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    Collections.sort(list, Collections.reverseOrder()); // 降序
    int l = list.size() - 1;
    int i = 0;
    while (i < l) {
        if (list.get(i).equals(list.get(i + 1))) {
            list.remove(i);
            // 删完不前进,长度同步减一
            i--;
            l--;
        }
        i++;
    }
    return list.toArray(new Integer[0]);
}

// 方法 14:排序 + 双指针(类似 LeetCode 原题)
// 先排序,再原地去重(修改原数组),最后返回去重后的新数组
static int[] unique14(int[] arr) {
    if (arr.length == 0) return arr;
    Arrays.sort(arr);
    int slow = 0; // 慢指针:指向去重后区间的末尾(初始为第一个)
    for (int fast = 1; fast < arr.length; fast++) {
        // 当前项与去重区间最后一个元素不同,说明有新的值
        if (arr[fast] != arr[slow]) {
            // 将慢指针右移一步,并将新值写入到去重区间末尾
            arr[++slow] = arr[fast];
        }
    }
    // [0, slow] 是去重后的结果
    return Arrays.copyOf(arr, slow + 1);
}

第 4 类:Stream 与函数式(方法 15-17 )

策略原理:Java 8 引入的 Stream API 把"管道 + 操作"形式化。distinct() 内部用 LinkedHashSet 实现去重;Collectors.toMap 提供按业务键去重的便捷工具;reduce 则可以把累加过程显式表达出来。

// 方法 15:基于 Stream.distinct() 一行去重
static Integer[] unique15(Integer[] arr) {
    // distinct 内部基于 LinkedHashSet ,保序、O(n)、写法最短
    return Arrays.stream(arr)
            .distinct()
            .toArray(Integer[]::new);
}

// 方法 16:通过 Stream.filter + 外部 Set
// 演示如何在函数式管道里携带"已出现"的状态
static Integer[] unique16(Integer[] arr) {
    Set<Integer> seen = new HashSet<>();
    // filter 谓词带副作用:seen.add 返回 true 表示首次加入
    // 整个表达式语义:仅保留首次见到的元素
    return Arrays.stream(arr)
            .filter(seen::add)
            .toArray(Integer[]::new);
}

// 方法 17:通过 Collectors.toMap 按业务键去重
// 适合自定义对象按某字段去重的场景
static Integer[] unique17(Integer[] arr) {
    // 第三个参数是冲突合并函数:(existing, replacement) -> existing 保留首项
    // 用 LinkedHashMap::new 作为 Map 工厂,保留插入顺序
    Map<Integer, Integer> map = Arrays.stream(arr).collect(
            Collectors.toMap(
                    item -> item,                  // keyMapper:用值本身做键
                    item -> item,                  // valueMapper
                    (existing, replacement) -> existing,
                    LinkedHashMap::new
            )
    );
    return map.values().toArray(new Integer[0]);
}

第 5 类:递归与位图(方法 18-20 )

策略原理:递归用自调用替代循环,是函数式思维的体现,主要用于教学与算法练习;位图(BitSet)用每一位标记一个非负整数是否出现过,对整数集合有极致的空间效率( 10 亿个 int 只要 128MB ),是大数据去重的常见选型。

// 方法 18:递归--从后往前构建去重列表,跟方法 1 类似,也是新建数组获得不重复项
// 递归检查末尾元素是否在前面出现过,若不重复则插入到结果列表头部,最终保持原顺序
static Integer[] uniqueRecursive(Integer[] arr, int length, List<Integer> result) {
    // 递归终止:只剩一项,直接收尾
    if (length <= 1) {
        if (length == 1) result.add(0, arr[0]);
        return result.toArray(new Integer[0]);
    }

    int last = length - 1;
    Integer lastItem = arr[last];
    boolean isRepeat = false;
    // 在 [0, last) 区间找是否已出现过 lastItem
    for (int i = last - 1; i >= 0; i--) {
        if (lastItem.equals(arr[i])) {
            isRepeat = true;
            break;
        }
    }
    // 不重复则把 lastItem 加入结果(往前插入以保持原顺序)
    if (!isRepeat) {
        result.add(0, lastItem);
    }
    return uniqueRecursive(arr, length - 1, result);
}

// 方法 19:递归--拼接返回(纯函数式,不修改原数组和外部集合)
// 核心思路:先递归处理前 n-1 个元素得到去重结果,再判断第 n 个元素是否在它前面出现过。
//         若未出现过,则追加到结果末尾;否则直接返回。最终保持原数组的首次出现顺序。
static List<Integer> uniqueRecursiveConcat(List<Integer> list, int length) {
    if (length <= 1) {
        // 长度≤1 时,直接返回原列表的前 length 个元素(避免后续越界)
        return new ArrayList<>(list.subList(0, length));
    }

    int last = length - 1;
    Integer lastItem = list.get(last);
    boolean isRepeat = false;
    // 检查当前项 lastItem 是否在它前面的子数组中出现过
    for (int i = last - 1; i >= 0; i--) {
        if (lastItem.equals(list.get(i))) {
            isRepeat = true;
            break;
        }
    }

    // 递归深入:此时尚未得到前 length-1 项的去重结果,暂停当前层,进入子问题求解
    List<Integer> head = uniqueRecursiveConcat(list, length - 1);
    // 递归回溯:子问题已返回结果( head 中已包含前 length-1 项的去重结果,且保序)
  
    // 当前项不重复则追加到结果末尾(保持原顺序)
    if (!isRepeat) {
        head.add(lastItem);
    }
    return head;
}

// 方法 20:BitSet 位图法(仅适用于非负整数)
// BitSet 是用一个 long[] 数组来模拟一个无限长的位序列。( 0 表示 false/未出现,1 表示 true/已出现)
// 每个 int 用一位标记是否出现过,10 亿规模也只要 ~128MB
static int[] uniqueBitSet(int[] arr) {
    BitSet bitSet = new BitSet();
    int[] result = new int[arr.length];
    int x = 0;
    for (int item : arr) {
        // 注意 BitSet 的下标必须非负,负数需要先做偏移
        if (item < 0) {
            throw new IllegalArgumentException("BitSet 不支持负数,需要先偏移");
        }
        // 第 item 位为 0 表示首次出现
        if (!bitSet.get(item)) {
            bitSet.set(item); // 标记为已出现
            result[x++] = item;
        }
    }
    return Arrays.copyOf(result, x);
}

实际项目里怎么选?

详细请见原文: https://github.com/microwind/algorithms/blob/main/array/unique/Java%E6%95%B0%E7%BB%84%E5%8E%BB%E9%87%8D%E7%9A%8420%E7%A7%8D%E6%96%B9%E5%BC%8F.md

AI 时代,程序员不一定要手写代码,但一定要懂得编程思路,这样才能更好地指导 AI 。

更多算法