【解法3】优化解:遍历链表两次

原始讲义 · 非增强校订版 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照

## 【解法3】优化解:遍历链表两次

(1)要找到倒数第k个结点,但链表不能回退,所以需要知道他是从前往后数第t个结点(不算头结点),t=n-k+1,n是总结点数,那我们只需要先遍历一次链表求出n,然后再遍历一次链表找到第t个结点。

(2) 算法如下:

(3) // 时间复杂度:O(n) 空间复杂度:O(1)
## 【解法4】链表操作,前后指针 (1)初始时设置两个指针left和right都指向头结点,right指针先后移k次,若此过程中left指向了NULL则查找失败,返回0。然后将left和right同时后移,直到right指向NULL为止,因为left和right直接相差k次移动,所以此时left一定指向倒数第k个结点,输出left,返回1。 (2) 算法如下:
int ans(node* list, int k)
{
    node* left=list;
    node* right=list;
    for (int i=0; i<k; i++)
    {
        right=right->link;
        if (right==NULL) return 0;
    }
    while(right!=NULL)
    left=left->link;
    right=right->link;
}
printf("%d\n", left->data);
return 1;
//链表长度不足k
//输出倒数第k个元素的值
<!-- page 99 --> (3) 时间复杂度:O(n) 空间复杂度:O(1) ## 2012年42题 假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,则可共享相同的后缀存储空间,例如,“loading”和“being”的存储映像如下图所示。
原资料配图
原资料配图 · 转写内容请回源核验

设str1和str2分别指向两个单词所在单链表的头结点,链表结点结构为:

<table><tr><td>data</td><td>next</td></tr></table>

请设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后缀的起始位置(如图中字符i所在结点的位置p)。要求:

1)给出算法的基本设计思想。

2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。

3)说明你所设计算法的时间复杂度。

<table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>预计得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>枚举</td><td rowspan=1 colspan=1>O(n2)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>11</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>链表转数组</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>13</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>多指针法</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>15</td></tr></table>

请注意本题是返回地址,而对于相同后缀来说,只要发现两个链指向了同一个结点,后面的部分也会相同。即地址相同等价于后缀完全相同。 20周

## 【解法1】暴力解:枚举str1和str2起始位置

(1)设str1和str2的长度较大的值是n,两层for循环枚举str1和str2相同后缀的起始位置。

(2) 算法如下:

Node* ans(Node *str1, Node *str2)
{
    Node* p1=str1-&gt;next;
    Node* p2;
    while (p1!=NULL)
    {
        p2=str2-&gt;next;
        while (p2!=NULL)
        {
            if (p1==p2)
            return p1;
            p2=p2-&gt;next;
        }
        p1=p1-&gt;next;
    }
    return NULL;
}
<!-- page 100 -->
(3) // 时间复杂度:O(n²) 空间复杂度:O(1)
【解法2】暴力解:链表转数组,数组保存结点地址 (1)设str1和str2的长度较大的值是n.设置两个新数组S1和S2分别保存str1和str2链中的每个结点的地址(这里用为了方便,从S1[1]和S2[1]开始存)。 (2) 算法如下:
Node* ans(Node *str1, Node *str2)
{
    Node* S1[maxn], S2[maxn]; // 保存str1和str2各结点地址,maxn是最大可
    能长度
    Node* p1=str1-&gt;next; // str1的后缀起始位置
    Node* p2=str2-&gt;next; // str2的后缀起始位置
    int Len1=0, Len2=0;
    while (p1!=NULL)
    {
        S1[Len1]=p1;
        Len1++;
        p1=p1-&gt;next;
    }
    while (p2!=NULL)
    { S2[Len2]=p2;
        Len2++;
        p2=p2-&gt;next;
    }
    if (S1[Len1-1]!=S2[Len2-1])
    return NULL; //无相同后缀,可不写
    for (int i=1; i&lt;=min(Len1, Len2); i++) // 讨论相同后缀长度
    if (S1[Len1-i]!=S2[Len2-i])
    return S1[Len1-i+1]; //输出起始地址
    return s1[0]; // 如果S1和S2完全相同则返回他们的第一个字母
    地址 机考研
}
(3) 时间复杂度:O(n) 空间复杂度:O(n) 【解法3】优化解:多指针法,前后指针 (1)每个链表设置一个指针,先求出两个链的长度,然后长的链指针往后移动x次(x是长度差的绝对值),使得两链剩余未访问结点数相同,然后两链表的指针同步往后移动比较,直到指向的是同一个结点。 (2) 算法如下: <!-- page 101 -->
Node* ans(Node *str1, Node *str2)
{
    Node* p1=str1-&gt;next; // 指针p1用来遍历str1
    Node* p2=str2-&gt;next; // 指针p2用来遍历str2
    int Len1=0, Len2=0;
    while (p1!=NULL)
    {
        Len1++;
        p1=p1-&gt;next;
    }
    while (p2!=NULL)
    { 十
        Len2++;
        p2=p2-&gt;next;
    }
    p1=str1-&gt;next;
    p2=str2-&gt;next;
    if (Len1&gt;Len2)
    for (int i=0; i&lt;Len1-Len2; i++) // str1更长,p1先移动一段
    p1=p1-&gt;next;
    else
    for (int i=0; i&lt;Len2-Len1; i++) // str2更长,p2先移动一段
    p2=p2-&gt;next;
    while (p1!=NULL &amp;&amp; p2!=NULL)
    {
        if (p1==p2) return p1; //返回相同后缀起始地址
        p1=p1-&gt;next;
        p2=p2-&gt;next;
    } return NULL; // 无相同后缀,可不写
}
(3)时间复杂度:O(n) 空间复杂度:O(1) ## 2015年41题 (15 分)用单链表保存m 个整数,结点的结构为[data,link],且|datal≤n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,对于链表中data的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。例如,若给定的单链表HEAD如下:
原资料配图
原资料配图 · 转写内容请回源核验

则删除结点后的 HEAD 为

<!-- page 102 -->

原资料配图
原资料配图 · 转写内容请回源核验

要求:

(1)给出算法的基本设计思想。

(2)使用C或C++语言,给出单链表结点的数据类型定义。

(3)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。

(4)说明你所设计算法的时间复杂度和空间复杂度。

<table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>预计得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>枚举</td><td rowspan=1 colspan=1>O(m²)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>11</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>散列表</td><td rowspan=1 colspan=1>O(m)</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>15</td></tr></table>

## 【解法1】暴力解:枚举,每一个结点都和其他结点比较

(1)两层循环枚举所有结点的两两比较,外层遍历链表中的结点p,内层q遍历p之后的结点,将和p绝对值相等的结点删除(只保留第一个出现的p)。

(2) 结点数据结构定义:

typedef struct Node{
    int data;
    struct Node *link;
}Node;
(3) 算法如下: <!-- verified-by-hand --> > [!success] 已结合原文第 102 页及 Markdown 上下文人工校对 > q 未声明,if/else 缺花括号,p 更新与函数闭括号错位。
void ans(Node *HEAD)
{
    Node *p = HEAD-&gt;link;
    while (p != NULL)
    {
        Node *pre = p;
        while (pre-&gt;link != NULL)
        {
            Node *q = pre-&gt;link;
            if (abs(p-&gt;data) == abs(q-&gt;data))
            {
                pre-&gt;link = q-&gt;link;
                free(q);
            }
            else pre = pre-&gt;link;
        }
        p = p-&gt;link;
    }
}
<!-- page 103 -->
(3) // 时间复杂度: O(m²) 空间复杂度:O(1)
【解法2】优化解:散列表 (1)data的绝对值≤n,那可以用数组count[0\~n]来保存对于每一个绝对值是否出现过,比如count[1]>0表示该绝对值已经出现过了,之后再出现就可以删除当前访问的结点。遍历一次链表,更新count数组以及删除后面重复出现的结点。 (2) 结点数据结构定义:
typedef struct Node{
    int data; kaoya //该结点权值
    struct Node *link; // 下一个结点
}Node;
(3) 算法如下:
void ans(Node* HEAD, int m, int n)
{
    Node* p=HEAD-&gt;link; // 当前访问结点p
    Node* pre=HEAD; // p的前一个结点pre
    int count[n+1]={0};
    while (p!=NULL)
    {
        if (count[abs(p-&gt;data)]==0)
        {
            count[abs(p-&gt;data)]=1; // p的权值第一次出现
            pre=p;
            p=p-&gt;next;
        }
        else
        {
            pre-&gt;link=p-&gt;link;
            free(p);
            p=pre-&gt;link; WW
        }
    }
}
<!-- code-audit: false_positive --> > [!info] 已人工审读:这是复杂度结论正文,被错误放入 C 代码围栏,不是程序代码。
(4)时间复杂度:O(m)空间复杂度:O(n)
<!-- page 104 --> (13分)设线性表采用带头结点的单链表保存,链表中的结点定义如下:
typedef struct node {
    int data;
    struct node* next;
} NODE;
请设计一个空间复杂度为 O(1)且时间上尽可能高效的算法,重新排列L中的各结点,得到线性表L = (a₁, an, a2,an-1,a3, an-2…)。要求: 车研 (1)给出算法的基本设计思想。 (2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。 (3)说明你所设计的算法的时间复杂度。 <table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>预计得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>枚举,直接插入</td><td rowspan=1 colspan=1>O(n²)</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>12</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>逆置+多指针法</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>15</td></tr></table> ## 【解法1】暴力解:枚举,直接插入 (1) 步骤如下: 1. 遍历链表,统计链表的结点数n。 2. 将链表分成两半,前一半是(n+1)/2 个结点,后一半n/2 个结点。 3. 将链表后半的所有结点依次插入。 (2) 算法如下:
void ans(Node* L)
{
    // 统计链表结点数
    Node *p = L-&gt;next;
    int n = 0;
    while (p != NULL)
    {
        n++;
        p = p-&gt;next;
    }
    //链表分成两半,p指向后一半第一个结点
    p = L;
    for (int i=0; i&lt;(n+1)/2; i++)
    p = p-&gt;next;
    Node *temp = p;
    p = p-&gt;next;
    temp-&gt;next = NULL;
    //将后一半结点依次插入到前一半
    for (int i=0; i&lt;n/2; i++)

    // 更新下一个待处理元素 p
    {
        temp = p-&gt;next;
        Node *pre = L;
        for (int j=0; j&lt;n/2-i; j++)
        pre = pre-&gt;next;
        p-&gt;next = pre-&gt;next;
        pre-&gt;next = p;
        p = temp;
    }
}
<!-- page 105 -->
(3) // 时间复杂度:O(n²)
## 【解法2】优化解:逆置+多指针法 (1) 步骤如下: 1. 前后指针找到中间结点。 2. 从中间结点处将链表分成两半,前一半是(n+1)/2 个结点,后一半 n/2 个结点。 3. 使用头插法将链表后半的所有结点逆置。 4. 将前一半链表和逆置后的后一半链表交叉排列合并成一个链表。 (2) 算法如下:
//头插法
void Head_Insert(Node* L, Node* p)
{
    p-&gt;next=L-&gt;next;
    L-&gt;next=p;
}
void ans(Node* L)
{
    // 找中间结点 p
    Node *p, *q;
    p = q = L-&gt;next;
    while (p != NULL &amp;&amp; q != NULL)
    {
        q = q-&gt;next;
        if (q == NULL || q-&gt;next == NULL)
        break;
        q = q-&gt;next;
        p = p-&gt;next;
    }
    // 链表从 p 后分成两半
    Node *temp = p;
    p = p-&gt;next;
    temp-&gt;next = NULL;
    //为链表后一半设置新的头结点
    Node *L2 = (Node*)malloc(sizeof(Node));
    L2-&gt;next = NULL;
    //将后一半结点逆置
    while (p != NULL)
    {
        temp = p-&gt;next;
        Head_Insert(L2, p);
        p = temp;
    }
    Node *p1 = L-&gt;next; // 机考研 o m
    Node *p2 = L2-&gt;next;
    // 将前一半链表和逆置后的后一半链表交叉合并
    while (p2 != NULL)
    {
        暂存 p1 和 p2 的下一个结点
        Node* temp1 = p1-&gt;next;
        Node* temp2 = p2-&gt;next;
        // p2 插入到 p1 后面
        p2-&gt;next = p1-&gt;next;
        p1-&gt;next = p2;
        // 更新 p1 和 p2
        p1 = temp1;
        p2 = temp2;
    }
}
<!-- page 106 --> (3) 时间复杂度:O(n)