【解法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->next!=NULL && pre->next->exp>p->exp) // 查找插入位置
pre=pre->next;
Linklist temp=pre->next;
//将p插入到pre后面
if (temp==NULL || temp->exp<p->exp)
{
p->next=temp;
pre->next=p;
return;
}
//次数相等,合并同类型
temp->data+=p->data;
if (temp->data==0) //如果相加后系数=0则删除
pre->next=temp->next;
}
LinkList ans(LinkList L1, LinkList L2)
{
LinkList_p=L2->next; // p2遍历链表L2
while(p!=NULL)
{
LinkList temp=p->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->next=pre->next; pre->next=p;
}
LinkList ans(LinkList L1, LinkList L2)
{
LinkList p1=L1->next; // p1遍历L1表
LinkList p2=L2->next; //p2遍历L2表
LinkList pre1=L1; // pre1是p1前一个结点
while (p1!=NULL && p2!=NULL)
{
// p1的指数更高则继续遍历L1
{
pre1=p1;
p1=p1->next;
}
else if(p1->exp<p2->exp) // p1的指数更低则将p2插入pre1后
Linklist temp=p2->next;
Insert(pre1,p2);
pre1=p2;
} p2=temp;
else //p1、p2指数相同则相加并判断是否删除
{
p1->data+=p2->data;
if (p1->data==0) // 若为0则删除p1结点
pre1->next=p1->next;
else
pre1=p1; // 若不为0则更新pre1
p1 = p1->next;
p2 = p2->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->next; // p1遍历L1表
LinkList pre1=L1;
while (p1!=NULL)
{
LinkList p2=L2->next; // p2遍历L2表
while(p2!=NULL)
{
if (p1->data==p2->data) break;
p2=p2->next;
}
if(p2==NULL)
pre1->next=p1->next; //没出现过则删除结点p1,pre1不需要更新
else
pre1=pre1->next; //出现过则不删除结点p1,pre1后移
p1=p1->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->next; // p1遍历L1表
LinkList p2=L2->next; //p2遍历L2表
LinkList pre1=L1; //pre1是p1前一个结点
while (p1!=NULL && p2!=NULL)
{
if (p1->data<p2->data) //p1->data小则删除p1结点,p1前移继续寻找
{
pre1->next=p1->next;
p1=p1->next;
}X
else if(p1->data>p2->data) // p2->data 小,p2前移继续寻找
p2=p2->next;
else
//p1、p2的data相同则保留p1结点,p1、p2都前移继续寻找
{
pre1=p1; // 更新pre1
p1=p1->next; p2=p2->next; // 首
}
while (p1 != NULL)
{ //如果链表L1还有剩余元素也删去他们
pre1->next = p1->next;
p1 = pre1->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<=k; i++)
{
p=list->link;
if (q==p) return 0; //找不到倒数第k个结点
while (p->link!=q)
p=p->link; // p最终是倒数第i个结点
q=p;
}
printf("%d\n", p->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->link; //正在遍历的结点
int n=0; //数组中结点个数
{
A[n++]=p;
p=p->link;
}
if (n<k) return 0;
printf("%d\n",A[n-k]->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->link;
int t, n=0;
while (p!=NULL)
n++;
p=p->link;
}
t=n-k+1;
if (t<=0) return 0;
p=list; // 从头结点开始往后移t次
for (int i=0; i<t; i++)
p=p->link;
printf("%d\n", p->data); // 输出倒数第k个元素的值
<!-- page 138 -->
<!-- code-audit: false_positive -->
> [!info] 已人工审读:该两行是上一页跨页代码块的结尾,单独扫描才出现花括号不平衡。
(3)时间复杂度:O(n)空间复杂度:O(1)