跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
|注册
博客列表

目录

  1. 问题描述
  2. 题目要求
  3. 设计思路
  4. 1. 暴力匹配法
  5. 代码结构分析
  6. 时间复杂度计算
  7. 2. 双指针法
  8. 思路
  9. 实现步骤
  10. C++ 实现代码
  11. 代码分解与讲解
  12. 为什么较长的链表要先移动?
  13. 算法时间复杂度分析
C++算法

求两个单链表公共后缀的起始位置算法实现与分析

本文探讨求解两个单链表公共后缀起始节点的问题。介绍了暴力匹配法和双指针法两种方案,重点分析了双指针法的优化思路:先计算长度差,对齐较长链表后再同步遍历。提供了完整的 C++ 代码实现及时间复杂度分析,指出双指针法可将复杂度从 O(n^3) 降低至 O(m+n)。

刀狂发布于 2026/3/29更新于 2026/4/131 浏览
求两个单链表公共后缀的起始位置算法实现与分析

问题描述

假设我们有一个带头结点的单链表来保存单词,每个节点包含一个字符和指向下一个节点的指针。若两个单词的后缀相同,它们可以共享相同的后缀存储空间。例如:

  • 单词 loading 和 being 有相同的后缀 ing,可以共享存储。
题目要求

设 str1 和 str2 分别指向两个单词所在链表的头节点,请设计一个尽可能高效的算法,找出两个链表共同后缀的起始位置。

要求:

  1. 给出算法的设计思想。
  2. 使用 C++ 代码实现算法,并在关键部分给出注释。
  3. 分析算法的时间复杂度。

设计思路

1. 暴力匹配法

最直接的思路是将链表 str1 的每一个节点依次与链表 str2 的节点进行匹配,如果匹配到相同的节点,则继续向后比较,直到找到第一个完全相同的后缀位置。

实现步骤:

  1. 从链表 str1 开始的每个节点,分别与 str2 中的节点开始逐一匹配。
  2. 若匹配的节点数据相同,继续匹配下一个节点;若不相同则跳到下一个节点。
  3. 当匹配到第一个后缀起始点时,返回该节点位置。
#include <bits/stdc++.h>
using namespace std;

struct Node {
    char value;
    Node* next;
};

bool check(Node* first, Node* second) {
    Node* l = first;
    Node* r = second;
    while (l != nullptr && r != nullptr) {
        if (l->value != r->value)
            return false;
        l = l->next;
        r = r->next;
    }
    return l == nullptr && r == nullptr;
}

Node* search(Node* first, Node* second) {
    Node* x = first;
    while (x != nullptr) {
        Node* y = second;
        while (y != nullptr) {
             (x->value == y->value && (x, y)) {
                 x;
            }
            y = y->next;
        }
        x = x->next;
    }
     ;
}

{
    Node* common =  Node{,  Node{,  Node{, }}};
    
    Node* str1 =  Node{, };
    str1->next =  Node{, };
    str1->next->next =  Node{, };
    str1->next->next->next =  Node{, };
    str1->next->next->next->next = common;

    Node* str2 =  Node{, };
    str2->next =  Node{, };
    str2->next->next = common; 

    
    Node* result = (str1, str2);
     (result != ) {
        cout <<  << ()result->value << endl;
    }  {
        cout <<  << endl;
    }
     ;
}
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • C++ 最大堆模拟:最后一块石头的重量
  • 智能车电磁组进阶:ADC 信号处理与差比和差算法
  • Spring 核心面试题:Bean 生命周期、AOP 与事务管理
  • Trae 编辑器中配置与使用 Go 语言的完整流程
  • C++ String 详解
  • MySQL 基础入门指南
  • ESP32 驱动 OV7670 摄像头实现简易照相机系统
  • Qwen3.5 开源发布:国产大模型性能与技术架构深度解读
  • Windows 下创建并激活 Python 虚拟环境 venv

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,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

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

if
check
return
return
nullptr
int main()
new
'i'
new
'n'
new
'g'
nullptr
// 创建两个测试链表
new
'l'
nullptr
new
'o'
nullptr
new
'a'
nullptr
new
'd'
nullptr
new
'b'
nullptr
new
'e'
nullptr
// 共享后缀部分
// 查找共同后缀的起始位置
search
if
nullptr
"共同后缀的起始位置:"
char
else
"没有共同后缀"
return
0
代码结构分析
  1. search 函数:这是主函数,它通过双层循环在链表 first 和 second 中查找共同后缀的起始位置。
    • 外层循环:遍历链表 first 的每个节点 x。
    • 内层循环:对于每个节点 x,遍历链表 second 的每个节点 y。
  2. check 函数:search 函数在 if(x->value == y->value && check(x, y)) 中调用 check 函数,用于比较链表 first 从节点 x 开始的后缀和链表 second 从节点 y 开始的后缀。
    • check 函数的时间复杂度为 O(n),因为它从两个节点开始逐一比较剩下的所有节点。
时间复杂度计算
  • 外层循环遍历链表 first 的每个节点,时间复杂度为 O(m)(m 是链表 first 的长度)。
  • 内层循环遍历链表 second 的每个节点,时间复杂度为 O(n)(n 是链表 second 的长度)。
  • check 函数的复杂度为 O(k),其中 k 是 x 和 y 之后的节点数量,最多接近 O(max(m, n))。

因此,整体时间复杂度为:O(m × n × k) ≈ O(n^3)

2. 双指针法

考虑一种更为高效的 双指针法。

思路

我们先遍历两个链表,得到它们的长度差 d。然后让较长的链表先走 d 步,这样两个链表剩下的节点数相等。之后同时遍历两个链表,直到找到第一个相同的节点,即为共同后缀的起始位置。

实现步骤
  1. 计算两个链表的长度 len1 和 len2,求得它们的长度差 d。
  2. 将较长的链表向前移动 d 步。
  3. 同时遍历两个链表,直到找到第一个相同的节点。

优点:通过两次遍历(计算长度 + 查找共同节点),时间复杂度降低到 O(m + n)。

C++ 实现代码

以下是使用 C++ 实现的双指针法代码:

#include <bits/stdc++.h>
using namespace std;

struct Node {
    char value;
    Node* next;
};

// 计算链表长度
int getLength(Node* node) {
    int length = 0;
    while (node != nullptr) {
        length++;
        node = node->next;
    }
    return length;
}

// 查找共同后缀起始节点
Node* search(Node* n, Node* m) {
    int len1 = getLength(n);
    int len2 = getLength(m);
    // 长链表先走 |len1 - len2| 步
    while (len1 > len2) {
        len1--;
        n = n->next;
    }
    while (len2 > len1) {
        len2--;
        m = m->next;
    }
    // 双指针同时遍历寻找共同节点
    while (n != nullptr && m != nullptr) {
        if (n == m)
            return n;
        n = n->next;
        m = m->next;
    }
    return nullptr;
}

int main() {
    Node* common = new Node{'i', new Node{'n', new Node{'g', nullptr}}};
    Node* str1 = new Node{'l', nullptr};
    str1->next = new Node{'o', nullptr};
    str1->next->next = new Node{'a', nullptr};
    str1->next->next->next = new Node{'d', nullptr};
    str1->next->next->next->next = common;
    Node* str2 = new Node{'b', nullptr};
    str2->next = new Node{'e', nullptr};
    str2->next->next = common;
    Node* result = search(str1, str2);
    if (result != nullptr) {
        cout << "共同后缀的起始位置:" << (char)result->value << endl;
    } else {
        cout << "没有共同后缀" << endl;
    }
    return 0;
}

这段代码的核心思想是通过调整两个链表的起始位置,使得它们在同一个位置开始比较,以便找到第一个相同的节点作为共同后缀的起始点。

代码分解与讲解
#include <bits/stdc++.h>
using namespace std;

struct Node {
    char value;
    Node* next;
};
  • 这里定义了链表节点的结构体 Node,包含一个字符型数据 value 和一个指向下一个节点的指针 next。
int getLength(Node* node) {
    int length = 0;
    while (node != nullptr) {
        length++;
        node = node->next;
    }
    return length;
}
  • getLength 函数用于计算链表的长度。它遍历链表并计数,直到链表末尾。时间复杂度为 O(n),其中 n 是链表的长度。
Node* search(Node* n, Node* m) {
    int x = getLength(n);
    int y = getLength(m);
  • search 函数是主函数,用来找到两个链表的共同后缀。首先调用 getLength 获取链表 n 和 m 的长度,分别记为 x 和 y。
为什么较长的链表要先移动?

假设链表 n 比链表 m 长。如果直接从头开始同时遍历两个链表,由于长度不相等,较长链表的指针会先到达结尾,而我们需要两个链表在'对齐的起始位置'进行比较。

因此,为了让两个链表末尾部分对齐:

  1. 让较长的链表先移动 |x - y| 步。这样移动后,两个链表的剩余部分长度相等。
  2. 从这个新位置开始,两个链表的指针将同步向后遍历,确保同时到达链表的末尾。
while (x > y) { x--; n = n->next; }
while (y > x) { y--; m = m->next; }
  • 在这里,较长的链表会先移动 |x - y| 步,以确保后续同步遍历时,两链表末尾部分的节点数相同。
while (n != nullptr && m != nullptr) {
    if (n == m) return n;
    n = n->next;
    m = m->next;
}
return nullptr;
  • 现在,两个链表 n 和 m 同时从新的位置开始遍历。
  • 如果发现 n 和 m 指向同一个节点(即它们有相同的地址),则该节点即为第一个公共节点,返回该节点。
  • 如果遍历结束未找到公共节点,则返回 nullptr,表示没有共同后缀。
算法时间复杂度分析
  1. 计算长度:
    • 函数 getLength 分别计算链表 n 和 m 的长度,耗时为 O(m) 和 O(n)。
  2. 对齐链表长度:
    • 假设 str1 比 str2 长。为了让两个链表的尾端对齐,较长的链表 str1 向前移动 |m - n| 步。无论如何,对齐过程的时间复杂度是 O(|m - n|),这可以简化为 O(m + n)。
  3. 寻找共同后缀:
    • 接下来,两个链表同时从对齐位置向后遍历,寻找第一个相同的节点。这部分的时间复杂度为 O(min(m, n))。

因此,整个算法的时间复杂度为 O(m + n)。这种算法之所以更高效,是因为它只需要遍历每个链表最多两次。

  • Java 代码块详解:实例、静态与同步
  • Antigravity:谷歌推出的免费 AI 编程工具评测与使用指南
  • Windows 多版本 JDK 配置及 JAVA_HOME 切换失效排查
  • C++ STL 算法实战指南
  • Android WebRTC SDK 入门指南:从零搭建实时音视频通信应用
  • Python 打造 AI 三剑客:文档总结、代码生成与资料检索
  • C++ AIGC 延迟优化核心技术与实战策略
  • VLA机器人革命:解析当下10篇最关键的视觉-语言-动作模型论文
  • Mac Mini M4 本地部署大模型:Ollama 与 Llama 环境配置
  • OpenClaw 本地部署及远程监控实操教程
  • 前端接入腾讯云 ASR 实时语音识别实践