【解法2】多指针法+逆置

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

## 【解法2】多指针法+逆置

(1)①使用快慢指针找到链表的中点(slow指向后半段的前一个位置)。②翻转链表的后半段。③同时遍历前半段和翻转后的后半段,逐个比较结点的值。④如果所有结点的值都相等,则链表是对称的,返回1;否则,链表不是对称的,返回0。

(2) 代码如下:

<!-- verified-by-hand -->
> [!success] 已结合原文第 91 页及 Markdown 上下文人工校对
> 按本页四步算法说明校正:保留快慢指针、切断与后半段逆置,将 return 1 移到完成全部比较之后
ListNode* Head_Insert(ListNode *L, ListNode *p)
{
    ListNode *next = p-&gt;next;
    p-&gt;next = L-&gt;next;
    L-&gt;next = p;
    return next;
}

int IsPalindrome(linklist L)
{
    if (L == NULL || L-&gt;next == NULL) return 1;
    linklist fast = L-&gt;next, slow = L-&gt;next;
    while (fast-&gt;next != NULL &amp;&amp; fast-&gt;next-&gt;next != NULL)
    {
        fast = fast-&gt;next-&gt;next;
        slow = slow-&gt;next;
    }
    linklist p1 = L-&gt;next;
    linklist p2 = slow-&gt;next;
    slow-&gt;next = NULL;

    linklist L2 = (linklist)malloc(sizeof(node));
    L2-&gt;next = NULL;
    while (p2 != NULL)
        p2 = Head_Insert(L2, p2);

    p2 = L2-&gt;next;
    while (p1 != NULL &amp;&amp; p2 != NULL)
    {
        if (p1-&gt;data != p2-&gt;data) return 0;
        p1 = p1-&gt;next;
        p2 = p2-&gt;next;
    }
    return 1;
}
(3) // 时间复杂度:O(n)
<!-- page 92 --> ## 习题3-4 (13分)一元稀疏多项式用带头结点的单链表L1、L2按指数降序排列(其中L1的最高次项系数>=L2的最高次项指数),请设计一个时间上尽可能高效的算法,将这两个多项式相加,结果仍按降序保存在链表中(多项式为0的时候只有头结点),返回头指针L1。结点结构(data表示系数,exp表示指数,next指向下一个结点): <table><tr><td>exp</td><td>data 研</td><td>next</td></tr></table> 例:多项式f1(x) = 3x4 + 2x3 + 1,f2(x) = 3x4 − 2x2 + 2,结果 f3(x) = f1(x) + f2(x) = 2x3 − x2 + 3, 链表结构如下:
原资料配图
原资料配图 · 转写内容请回源核验
原资料配图
原资料配图 · 转写内容请回源核验

结果2X³-X²+3

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

(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(mn)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>9</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>多指针法</td><td rowspan=1 colspan=1>O(m + n)</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>13</td></tr></table>

## 【解法1】枚举法

对L2 的每一项从头遍历,尝试将其插入到L1 中合适的位置;

(1) 定义:

typedef struct Node{
    int data;
    int exp;
    struct Node *next;
}Node, *LinkList;
(2) 代码如下:
void Find_Insert(LinkList pre, LinkList p) // 查找并插入p
{
    while (pre-&gt;next!=NULL &amp;&amp; pre-&gt;next-&gt;exp&gt;p-&gt;exp) //查找插入位置
    pre=pre-&gt;next;
    Linklist temp=pre-&gt;next; // temp是pre后一个位置
    //将p插入到pre后面
    if (temp==NULL || temp-&gt;exp&lt;p-&gt;exp)
    {
        p-&gt;next=temp;
        pre-&gt;next=p;
        return;
    }
    //次数相等,合并同类型
    temp-&gt;data+=p-&gt;data;
    if (temp-&gt;data==0)
} pre-&gt;next=temp-&gt;next;
LinkList ans(LinkList L1, LinkList L2)
{
    LinkList p=L2-&gt;next;
    while (p!=NULL)
    {
        LinkList temp=p-&gt;next;
        Find_Insert(L1, p);
        p=temp;
    }
    return L1;
}
<!-- page 93 --> (3) 时间复杂度:O(mn) ## 【解法2】多指针法 设两个指针 p1 和 p2 分别遍历两个链表 L1 和 L2,另设一个 pre1 作为 p1 的前驱指针,通过同时比较两边结点的指数大小来进行归并操作,具体策略如下: • 若 p1->exp > p2->exp ,说明 L1 当前项指数更高, p1 向后推进; • 若 p1->exp < p2->exp ,说明 L2 当前项指数更高,将 p2 插入到 pre1 与 p1 之间; • 若指数相等,直接合并系数,若合并后系数为0,删除该项。 剩余的L2 结点(未合并完)可直接接到L1尾部。 (1) 定义:
typedef struct Node{
    int data;
    int exp;
    struct Node *next;
}Node, *LinkList;
(2) 代码如下:
void Insert(Linklist pre, LinkList p)
{
    p-&gt;next=pre-&gt;next;
    pre-&gt;next=p;
    LinkList ans(LinkList L1, LinkList L2)
    {
        LinkList p1=L1-&gt;next;
        LinkList p2=L2-&gt;next;
        LinkList pre1=L1;
        while (p1!=NULL &amp;&amp; p2!=NULL)
        {
            if (p1-&gt;exp&gt;p2-&gt;exp)
            {
                pre1=p1;
                p1=p1-&gt;next;
            }
            else if (p1-&gt;exp&lt;p2-&gt;exp)
            {
                Linklist temp=p2-&gt;next;
                Insert(pre1,p2);
                pre1=p2;
                p2=temp;
            }
            else
            {
                p1-&gt;data+=p2-&gt;data;
                if (p1-&gt;data==0)
                pre1-&gt;next=p1-&gt;next;
                else
                pre1=p1;
                p1 = p1-&gt;next;
                p2 = p2-&gt;next;
            }
        }
        if (p2!=NULL)
        pre1-&gt;next=p2;
        return L1;
    }
}
<!-- page 94 -->
// p1遍历L1表
// p2遍历L2表
// pre1是p1前一个结点
// p1的指数更高则继续遍历L1
// p1的指数更低则将p2插入pre1后
// p1、p2指数相同则相加并判断是否删除
//若为0则删除p1结点
//若不为0则更新pre1
//如果链表L2还未访问完则接到pre1后面
## (3) 时间复杂度: O(m + n) ## 习题3-5 (13分)设有两个从小到大排序(无重复元素)的带头结点的有序链表L1和L2。设计一个空间复杂度为O(1)且时间上尽可能高效的算法,求这两个链表交运算的算法(即L1∩L2)。要求结果链表仍是从小到大排序,但无重复元素。
typedef struct node{
    int data;
    struct node *next;
}node, *LinkList;
(1)给出算法的基本设计思想。 (2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。 (3)说明所设计算法的时间复杂度。 <!-- page 95 --> <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>9</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>13</td></tr></table> ## 【解法1】枚举法 (1)两层循环,外层循环遍历链表L1每一个结点,内层枚举链表L2来判断L1中的每个结点是否出现过,如果L1中的某个结点未出现过则删除。 1老 nm (2) 代码如下: <!-- verified-by-hand --> > [!success] 已结合原文第 95 页及 Markdown 上下文人工校对 > 外层 while 缺少块,‘结点’被断入代码,return 越出函数。
LinkList ans(LinkList L1, LinkList L2)
{
    LinkList p1 = L1-&gt;next;
    LinkList pre1 = L1;
    while (p1 != NULL)
    {
        LinkList p2 = L2-&gt;next;
        while (p2 != NULL &amp;&amp; p1-&gt;data != p2-&gt;data)
            p2 = p2-&gt;next;
        if (p2 == NULL) pre1-&gt;next = p1-&gt;next;
        else pre1 = p1;
        p1 = p1-&gt;next;
    }
    return L1;
}
(3) 时间复杂度:O(n²)
原资料配图
原资料配图 · 转写内容请回源核验

## 【解法2】多指针法

(1)指针p1、p2分别遍历链表L1和L2,pre保存p1前一个结点,每次比较p1和p2:①如果p1->data ==p2->data,说明找到了相同的结点,保留p1结点,然后p1、p2同时后移;②如果p1->data>p2->data,说明p2->data在p1中不会出现,并将p2后移;③如果p1->data<p2->data,说明p1->data在p2中不会出现,删除p1结点,并将p1后移。继续按照上述步骤遍历,直到遍历完一个链表。如果链表L1还未遍历完,则剩下的元素也不需要,删除p1及之后的元素,最后返回L1。1 a

(2) 代码如下:

LinkList ans(LinkList L1, LinkList L2)
{
    LinkList p1=L1-&gt;next;
}
<!-- page 96 --> <!-- verified-by-hand --> > [!success] 已结合原文第 96 页及 Markdown 上下文人工校对 > 清理 L1 剩余结点的 while 被错嵌在‘两值相等’分支,会在首次匹配后立即删光后续并返回。
LinkList ans(LinkList L1, LinkList L2)
{
    LinkList p1 = L1-&gt;next, p2 = L2-&gt;next, pre1 = L1;
    while (p1 != NULL &amp;&amp; p2 != NULL)
    {
        if (p1-&gt;data &lt; p2-&gt;data)
        {
            pre1-&gt;next = p1-&gt;next;
            p1 = p1-&gt;next;
        }
        else if (p1-&gt;data &gt; p2-&gt;data)
        {
            p2 = p2-&gt;next;
        }
        else
        {
            pre1 = p1;
            p1 = p1-&gt;next;
            p2 = p2-&gt;next;
        }
    }
    pre1-&gt;next = NULL;
    return L1;
}
(3) 时间复杂度: O(m + n) ## 3.7.2 真题 ## 2009年42题 (15分)已知一个带有表头结点的单链表,结点结构为 <table><tr><td>data</td><td>link</td></tr></table> 假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1;否则,只返回0。要求: (1)描述算法的基本设计思想。 (2)描述算法的详细实现步骤。 (3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C、C++或Java语言实现),关键之处请给出简要注释。 ka <!-- page 97 --> <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>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>0(1)</td><td rowspan=1 colspan=1>15</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)两层循环,外层是遍历i,内层是找到倒数第i个元素,比如上一轮查找到倒数第i-1个结点是q,那下一轮当p->link== q时p就是倒数第i个结点。 (2) 算法如下:
int ans(node* list, int k)
{
    node* p;
    node* q=NULL; // q=NULL是倒数第0个结点
    for (int i=1; i&lt;=k; i++)
    {
        p=list-&gt;link;
        if (q==p) return 0; // 找不到倒数第k个结点
        while (p-&gt;link!=q)
        p=p-&gt;link; // p最终是倒数第i个结点
        q=p;
        printf(&quot;%d\n&quot;, p-&gt;data); // 输出倒数第k个结点的值
    } return 1;
}
(3) // 时间复杂度:O(n²) 空间复杂度:O(1)
## 【解法2】优化解:链表转数组 (1)遍历链表,将所有结点都存放在数组中,最后输出数组中倒数第k个元素即可。 (2) 算法如下:
int ans(node* list, int k)
{
    node* A[maxn];
    node* p=list-&gt;link;
    int n=0;
    while (p!=NULL)
    {
        A[n++]=p;
        p=p-&gt;link;
    }
    if (n&lt;k) return 0;
    printf(&quot;%d\n&quot;, A[n-k]-&gt;data);
    return 1;
}
//结点数组
// 正在遍历的结点
//数组中结点个数
//输出倒数第k个元素的值
<!-- page 98 -->
int ans(node* list, int k)
{
    node* p=list-&gt;link;
    int t, n=0;
    {
        n++;
        p=p-&gt;link;
    }
    t=n-k+1;
    if (t&lt;=0) return 0;
    p=list; // 从头结点开始往后移t次
    for (int i=0; i&lt;t; i++)
    p=p-&gt;link;
    printf(&quot;%d\n&quot;, p-&gt;data); //输出倒数第k个元素的值
    return 1;
}
```c (3) // 时间复杂度:O(n) 空间复杂度:O(n) ```