跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

算法精讲:环形链表、数组交集与随机链表复制

针对环形链表检测、数组交集计算及随机指针复制等常见数据结构问题,分别提供了基于哈希集合与双指针的高效解法。文章重点演示了 C++ STL 在容器去重与映射查找中的应用,同时辅以 C 语言节点穿插技巧作为底层原理补充。通过实际代码示例,解析了如何避免重复元素干扰、处理复杂指针关系以及优化时间复杂度,适合希望巩固算法基础与提升代码实现的开发者参考。

林间仙子发布于 2026/3/15更新于 2026/6/1517 浏览
算法精讲:环形链表、数组交集与随机链表复制

一、环形链表检测

问题描述

给定一个链表的头节点 head,返回链表开始入环的第一个节点。如果链表无环,则返回 null。

为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。注意不允许修改链表。

解题思路

在之前的讲解中曾提到过 C 语言的快慢指针解法,虽然经典但实现相对繁琐。这里我们直接使用 C++ STL 的 set 容器来简化逻辑。核心思想是遍历链表,将访问过的节点指针存入集合中。如果当前节点已经在集合里,说明遇到了环的入口;如果遍历结束仍未发现重复节点,则无环。

需要注意的是,我们存储的是节点的指针地址,而非节点的值,因为不同节点可能拥有相同的值。

代码实现
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        std::set<ListNode*> s;
        ListNode* cur = head;
        while(cur) {
            auto it = s.find(cur);
            // 如果 cur 不在 set 对象中,就插入到 set 对象中
            if(it == s.end()) {
                s.insert(cur);
            } else {
                // cur 在 set 对象中就带环,并且该节点就是环入口点
                return *it;
            }
            cur = cur->next;
        }
        return nullptr;
    }
};

二、两个数组中的交集

问题描述

给定两个数组 nums1 和 nums2,返回它们的交集。输出结果中的每个元素一定是唯一的。我们可以不考虑输出结果的顺序。

解题思路

暴力遍历的方法存在隐患。例如示例 2 中,如果拿 nums1 中的值去 nums2 找没问题,但如果反过来或者处理重复元素不当,容易得到错误结果如 [9,4,9,4]。

正确的做法是先对两个数组进行去重。利用 STL 中的 set 天然去重的特性,或者排序后使用 std::unique。之后只需遍历其中一个去重后的集合,检查其元素是否存在于另一个集合中即可。

此外,对于有序集合,还可以使用双指针对比算法来寻找交、差、并集,这在数据同步等场景非常实用。比如在进行云端数据备份时,我们需要对比本地文件与服务器文件的差异,判断哪些需要上传或更新,原理与此类似。

代码实现
class Solution { public: vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { // 去重 std::set<int> s1(nums1.begin(), nums1.end()); std::set<int> s2(nums2.begin(), nums2.end()); vector<int> v; // 将一个数组中的值依次到另一个数组中查找 for(auto e : s1) { if(s2.count(e)) { v.push_back(e); // 在另一个数组中插入到一个容器中 } } return v; } };
对比算法扩展

由于 set 遍历是有序的,我们可以利用这一特性优化查找过程,无需嵌套循环。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        std::set<int> s1(nums1.begin(), nums1.end());
        std::set<int> s2(nums2.begin(), nums2.end());
        vector<int> ret;
        
        // 因为 set 遍历是有序的,有序值,依次比较
        // 小的++,相等的就是交集
        auto it1 = s1.begin();
        auto it2 = s2.begin();
        while(it1 != s1.end() && it2 != s2.end()) {
            if(*it1 < *it2) {
                it1++;
            } else if(*it1 > *it2) {
                it2++;
            } else {
                ret.push_back(*it1);
                it1++;
                it2++;
            }
        }
        return ret;
    }
};

三、随机链表的复制

问题描述

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random,该指针可以指向链表中的任何节点或空节点。构造这个链表的深拷贝。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点。

解题思路

这个问题难点在于 random 指针的映射关系。如果仅通过节点值来判断,当多个节点值相同时无法区分具体指向哪个节点。

方法一:C++ STL Map 映射 利用 map<Node*, Node*> 建立原链表节点与新链表节点的对应关系。先遍历一遍创建所有新节点并记录映射,再遍历一遍设置 random 指针。这种方法逻辑清晰,易于理解。

方法二:C 语言节点穿插(底层原理) 如果不依赖高级容器,可以在原链表每个节点后直接插入一个新节点(原->新->原->新...),这样新节点就能通过 原->next 找到对应的原节点,进而通过 原->random->next 找到新节点的 random 目标。最后再将新旧链表拆分。这种方法空间复杂度更低,但操作稍显复杂。

代码实现

C++ 版本(推荐)

class Solution {
public:
    Node* copyRandomList(Node* head) {
        std::map<Node*, Node*> nodeMap; // 存储原节点到拷贝节点的映射
        Node* copyhead = nullptr, *copytail = nullptr;
        Node* cur = head;
        
        // 第一步:创建新节点并建立映射
        while(cur) {
            if(copytail == nullptr) {
                copyhead = copytail = new Node(cur->val);
            } else {
                copytail->next = new Node(cur->val);
                copytail = copytail->next;
            }
            nodeMap[cur] = copytail;
            cur = cur->next;
        }
        
        // 第二步:处理 random 指针
        cur = head;
        Node* copy = copyhead;
        while(cur) {
            if(cur->random == nullptr) {
                copy->random = nullptr;
            } else {
                // 通过 map 获取原节点 random 对应的拷贝节点
                copy->random = nodeMap[cur->random];
            }
            cur = cur->next;
            copy = copy->next;
        }
        return copyhead;
    }
};

C 语言版本(了解思路)

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */
typedef struct Node Node;

// 构造一个新节点
Node* buyNode(int x) {
    Node* newnode = (Node*)malloc(sizeof(Node));
    newnode->val = x;
    newnode->next = newnode->random = NULL;
    return newnode;
}

// 在原链表的基础上拷贝节点(即原链表的每个节点后面拷贝一个一样的节点)
void AddNode(Node* head) {
    Node* pcur = head;
    while(pcur) {
        Node* newnode = buyNode(pcur->val);
        Node* next = pcur->next;
        newnode->next = next;
        pcur->next = newnode;
        pcur = next;
    }
}

// 设置新节点的 random,利用关系式:copy(random) = original(random)->next
void setRandom(Node* head) {
    Node* pcur = head;
    while(pcur) {
        Node* copy = pcur->next;
        if(pcur->random) {
            copy->random = pcur->random->next;
        }
        pcur = copy->next;
    }
}

struct Node* copyRandomList(struct Node* head) {
    if(head == NULL) return head;
    
    // 1. 在原链表基础上拷贝节点并插入到原链表的每个节点之后
    AddNode(head);
    // 2. 设置新节点的 random
    setRandom(head);
    // 3. 断开新旧链表
    Node* pcur = head;
    Node* copyHead, *copyTail;
    copyHead = copyTail = pcur->next;
    
    while(copyTail->next) {
        pcur = copyTail->next;
        copyTail->next = pcur->next;
        copyTail = copyTail->next;
    }
    return copyHead;
}

目录

  1. 一、环形链表检测
  2. 问题描述
  3. 解题思路
  4. 代码实现
  5. 二、两个数组中的交集
  6. 问题描述
  7. 解题思路
  8. 代码实现
  9. 对比算法扩展
  10. 三、随机链表的复制
  11. 问题描述
  12. 解题思路
  13. 代码实现
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AI 时代为何“人人都是产品经理”成为现实
  • Flutter 库 bavard 在鸿蒙端的适配实践:语义化消息协议与分布式通讯
  • 10 个热门 Claude Skills 开源仓库精选与安装配置指南
  • 智元机器人三大产线及技术产业化分析
  • 主流UI设计工具AI生成界面对比:Figma、墨刀、Pixso
  • PUBG 压枪宏配置教程:Logitech 鼠标自动化设置指南
  • IDEA 配置多 Git 账号实现项目隔离提交与拉取
  • Z-Image-Turbo 文生图模型部署与使用指南
  • AI 时代打造超级个体:个人效能提升与实战路径
  • 2026 年 3 月 AI 领域动态:多模态模型与开源生态热点梳理
  • JWT 原理详解及 Spring Boot 实战指南
  • AI 辅助前端逆向实践:Upwork 消息系统解析
  • llama.cpp 大模型本地部署指南
  • llama.cpp 本地部署性能调优指南:从启动瓶颈到推理效率优化
  • Android WebView 版本升级方案详解
  • 无人机智能巡检系统与大疆云 API 集成设计
  • C++ 递归实现合并两个有序链表与反转链表
  • Stable Diffusion 微调教程:基于 Dreambooth 与 LoRA
  • 借助 AI 快速逆向 Upwork 消息系统 API 与数据结构
  • 基于 WeKnora 开源项目的 RAG 与知识图谱架构解析

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online