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

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

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

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

//头插法:将p插入到以L 为头结点的链表开头
linklist Head_Insert(linklist L, linklist p)
{
    ListNode* temp=p->next;
    L->next=p;
    return temp;
}
int IsPalindrome(linklist L)
{
    if (L==NULL  L->next==NULL) //链表为空或一个结点时
    return 1;
    linklist fast=L->next, slow=L->next;
    //使用快慢指针找到中点 slow,使得slow->next 是后半段起点
    while (fast->next!=NULL && fast->next->next!=NULL)
    {
        fast=fast->next->next;
        slow=slow->next;
        linklist p2=slow->next; //后半段从slow->next开始
        linklist p1=L->next; //前半段从开始,到slow结束
        slow->next=NULL;
        //翻转链表的后半段
        linklist L2=(linklist)malloc(sizeof(node)); // 后半段链表头
        L2->next=NULL;
        while (p2!=NULL)
        p2=Head_Insert(L2,p2);
        p2=L2->next; // p2指向L2的第一个元素
        //比较前半段和翻转后的后半段的结点值
        while (p1!=NULL && p2!=NULL)
        if(p1->data!=p2->data)
        return 0;
        p1=p1->next;
        p2=p2->next;
        return 1;
    }
}
## (3) 时间复杂度:O(n) <!-- page 131 --> ## 习题3-4 (13分)一元稀疏多项式用带头结点的单链表L1、L2按指数降序排列(其中L1的最高次项系数>=L2的最高次项指数),请设计一个时间上尽可能高效的算法,将这两个多项式相加,结果仍按降序保存在链表中(多项式为0的时候只有头结点),返回头指针L1。结点结构(data表 <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
原资料配图
原资料配图 · 转写内容请回源核验

类型定义(2)采用C或C++语言描述算法,关键之处给出注释。
间复杂度。

(1) 写出链表结点的数据

(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) 代码如下: <!-- page 132 -->
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;
    //将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) //如果相加后系数=0则删除
    pre-&gt;next=temp-&gt;next;
}
LinkList ans(LinkList L1, LinkList L2)
{
    LinkList_p=L2-&gt;next; // p2遍历链表L2
    while(p!=NULL)
    {
        LinkList temp=p-&gt;next;
        Find_Insert(L1, p);
        p=temp;
    } return L1;
}
## (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;
<!-- page 133 --> ## (2) 代码如下: <!-- suspect-code --> > [!warning] 这段代码需要核对(原文第 133 页) > OCR 体检未通过:花括号不平衡。扫描件的两栏排版(代码 + 右侧注释)有时会被交错读取,产生看起来合理但语义错误的代码。背之前请对照原文。
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; // p1遍历L1表
    LinkList p2=L2-&gt;next; //p2遍历L2表
    LinkList pre1=L1; // pre1是p1前一个结点
    while (p1!=NULL &amp;&amp; p2!=NULL)
    {
        // p1的指数更高则继续遍历L1
        {
            pre1=p1;
            p1=p1-&gt;next;
        }
        else if(p1-&gt;exp&lt;p2-&gt;exp) // p1的指数更低则将p2插入pre1后
        Linklist temp=p2-&gt;next;
        Insert(pre1,p2);
        pre1=p2;
    } p2=temp;
    else //p1、p2指数相同则相加并判断是否删除
    {
        p1-&gt;data+=p2-&gt;data;
        if (p1-&gt;data==0) // 若为0则删除p1结点
        pre1-&gt;next=p1-&gt;next;
        else
        pre1=p1; // 若不为0则更新pre1
        p1 = p1-&gt;next;
        p2 = p2-&gt;next;
    }
}
if (p2!=NULL)
//如果链表L2还未访问完则接到pre1后面
return L1;
} HN
## (3) 时间复杂度:O(m + n) ## 习题3-5 (13分)设有两个从小到大排序(无重复元素)的带头结点的有序链表L1和L2。设计一个空间复杂度为O(1)且时间上尽可能高效的算法,求这两个链表交运算的算法(即L1∩L2)。要求结
typedef struct node{
    int data;
    struct node *next;
}node, *LinkList;
<!-- page 134 --> 给出注释。(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>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> 本题要求空间复杂度为O(1),所以不能使用转数组的方法。 ## 【解法1】枚举法 (1)两层循环,外层循环遍历链表L1每一个结点,内层枚举链表L2来判断L1中的每个结点是否出现过,如果L1中的某个结点未出现过则删除。(2)代码如下:
LinkList ans(LinkList L1, LinkList L2)
{
    LinkList p1=L1-&gt;next; // p1遍历L1表
    LinkList pre1=L1;
    while (p1!=NULL)
    {
        LinkList p2=L2-&gt;next; // p2遍历L2表
        while(p2!=NULL)
        {
            if (p1-&gt;data==p2-&gt;data) break;
            p2=p2-&gt;next;
        }
        if(p2==NULL)
        pre1-&gt;next=p1-&gt;next; //没出现过则删除结点p1,pre1不需要更新
        else
        pre1=pre1-&gt;next; //出现过则不删除结点p1,pre1后移
        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。 <!-- page 135 --> ## (2) 代码如下:
LinkList ans(LinkList L1, LinkList L2)
{
    LinkList p1=L1-&gt;next; // p1遍历L1表
    LinkList p2=L2-&gt;next; //p2遍历L2表
    LinkList pre1=L1; //pre1是p1前一个结点
    while (p1!=NULL &amp;&amp; p2!=NULL)
    {
        if (p1-&gt;data&lt;p2-&gt;data) //p1-&gt;data小则删除p1结点,p1前移继续寻找
        {
            pre1-&gt;next=p1-&gt;next;
            p1=p1-&gt;next;
        }X
        else if(p1-&gt;data&gt;p2-&gt;data) // p2-&gt;data 小,p2前移继续寻找
        p2=p2-&gt;next;
        else
        //p1、p2的data相同则保留p1结点,p1、p2都前移继续寻找
        {
            pre1=p1; // 更新pre1
            p1=p1-&gt;next; p2=p2-&gt;next; // 首
        }
        while (p1 != NULL)
        { //如果链表L1还有剩余元素也删去他们
            pre1-&gt;next = p1-&gt;next;
            p1 = pre1-&gt;next;
            return L1;
        }
    }
}
## (3) 时间复杂度:O(m + n) <!-- page 136 --> ## 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语言实现),关键之处请给出简要注释。 <table><tr><td rowspan=1 colspan=1>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>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;
}
<!-- page 137 --> (3)时间复杂度:O(n²)空间复杂度:O(1) 【解法2】优化解:链表转数组(1)遍历链表,将所有结点都存放在数组中,最后输出数组
int ans(node* list, int k)
{
    node* A[maxn]; //结点数组
    node* p=list-&gt;link; //正在遍历的结点
    int n=0; //数组中结点个数
    {
        A[n++]=p;
        p=p-&gt;link;
    }
    if (n&lt;k) return 0;
    printf(&quot;%d\n&quot;,A[n-k]-&gt;data);// 输出倒数第k个元素的值
    return 1;
}
(3)时间复杂度:O(n) 空间复杂度:O(n) ## 【解法3】优化解:遍历链表两次 (1)要找到倒数第k个结点,但链表不能回退,所以需要知道他是从前往后数第t个结点(不算头结点),t=n-k+1,n是总结点数,那我们只需要先遍历一次链表求出n,然后再遍历一次链表找到第t个结点。(2)算法如下: V
int ans(node* list, int k)
{
    node* p=list-&gt;link;
    int t, n=0;
    while (p!=NULL)
    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个元素的值
<!-- page 138 --> <!-- code-audit: false_positive --> > [!info] 已人工审读:该两行是上一页跨页代码块的结尾,单独扫描才出现花括号不平衡。
return 1;
}
(3)时间复杂度:O(n)空间复杂度:O(1)