3.6 章节练习

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

## 3.6 章节练习

## 3.6.1 习题

【习题3-1】(13分)已知两个长度分别为m和n带头结点的升序链表L1和L2,若将它们合并为一个长度为m+n的升序链表。请设计一个时间和空间上尽可能高效的算法,返回合并后的结点的结构定义如下:

typedef struct ListNode {
    int val;
    struct ListNode* next;
} ListNode;
要求:(1)给出算法的基本设计思想。(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。(3)说明你所设计算法的时间复杂度和空间复杂度。 <!-- page 117 --> 【习题3-2】(13分)已知两个长度分别为m和n带头结点的升序链表L1和L2,若将它们合并为一个长度为m+n的降序链表,头结点为L3。请设计一个时间和空间上尽可能高效的算法,返回合并后的带头结点的降序链表,其头结点为L3。结点的结构定义如下:
typedef struct ListNode {
    int val;
    struct ListNode* next;
} ListNode;
要求:(1)给出算法的基本设计思想。(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。(3)说明你所设计算法的时间复杂度和空间复杂度。 <!-- page 118 --> 【习题3-3】(13分)设计一个空间复杂度为O(1)且时间上尽可能高效的算法intIsPalindrome(linklist L),判断一个带头结点的单链表(表头指针为L,链表长度为n)除头结点外是否对称,若对称,则返回1,否则返回0。例如,表(25,34,34,25)和表(25,3,25)都是对称的,表(25,3,30)不是对称的。数据类型定义如下:
typedef struct node{
    int data;
    struct node *next;
}node, *linklist;
(1)给出算法的基本设计思想。(2) 根据设计思想,采用C或C++语言描述算法,关键之处给出注释。(3)说明所设计算法的时间复杂度。 <!-- page 119 --> 【习题3-4】(13分)一元稀疏多项式用带头结点的单链表L1、L2按指数降序排列(其中L1的最高次项系数>=L2的最高次项指数),请设计一个时间上尽可能高效的算法,将这两个多项式相加,结果仍按降序保存在链表中(多项式为0的时候只有头结点),返回头指针L1。结点结构(data表示系数,exp表示指数,next指向下一个结点): <table><tr><td rowspan=1 colspan=1>exp</td><td rowspan=1 colspan=1>data</td><td rowspan=1 colspan=1>next</td></tr></table> 例:多项式f1(x) = 3x4 + 2x3 + 1, f2(x) = 3x4 − 2x2 + 2, f3(x) = f1(x) + f2(x) = 2x3 − x2 + 3, 链表结构如下:
原资料配图
原资料配图 · 转写内容请回源核验

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

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

(3) 说明所设计算法的时

<!-- page 120 -->

【习题3-5】(13分)设有两个从小到大排序(无重复元素)的带头结点的有序链表L1和L2。设计一个空间复杂度为O(1)且时间上尽可能高效的算法,求这两个链表交运算的算法(即

typedef struct node{
    int data;
    struct node *next;
}node, *LinkList;
(1)给出算法的基本设计思想。(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。(3)说明所设计算法的时间复杂度。 价 <!-- page 121 --> ## 3.6.2 真题 【2009年42题】(15分)已知一个带有表头结点的单链表,结点结构为 <table><tr><td>data link</td></tr><tr><td></td></tr></table> 假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的 data 域的值,并返回1;否则,只返回0。要求:(1)描述算法的基本设计思想。(2)描述算法的详细实现步骤。(3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C、C++或 Java 语言实现),关键之处请给出简要注释。 MN <!-- page 122 --> 【2012年42题】42. 假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,则可共享相同的后缀存储空间,例如,“loading”和“being”的存储映像如下图所示。
原资料配图
原资料配图 · 转写内容请回源核验

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

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

请设计一个时间上尽可能高效的算法,找出由 str1 和 str2 所指向两个链表共同后缀的起始位置(如图中字符i所在结点的位置p)。要求:1)给出算法的基本设计思想。2)根据设计思想,采用C 或C++或Java语言描述算法,关键之处给出注释。3) 说明你所设计算法的时间复杂度。

<!-- page 123 -->

【2015 年 41 题】(15 分)用单链表保存 m 个整数,结点的结构为 [data,link],且|data|≤n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,对于链表中data的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。例如,若给定的单链表HEAD 如下: 成村

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

(1)给出算法的基本设计思想。(2)使用C 或C++语言,给出单链表结点的数据类型定义。(3)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。(4)说明你所设计算法的时间复杂度和空间复杂度。

<!-- page 124 -->

【2019年41题】(13分)设线性表采用带头结点的单链表保存,链表中的结点定义如下:

typedef struct node {
    int data;
    struct node* next;
} NODE;
请设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L 中的各结点,得到线性表L = (a1, an, a2, an−1, a3, an−2 …)。要求:(1)给出算法的基本设计思想。(2)根据设计思想,采用C 或C++语言描述算法,关键之处给出注释。(3)说明你所设计的算法的时间复杂度。 <!-- page 125 --> ## 3.7 章节答案 ## 3.7.1 习题 ## 习题3-1 (13分)已知两个长度分别为m和n带头结点的升序链表L1和L2,若将它们合并为一个长度为m+n的升序链表。请设计一个时间和空间上尽可能高效的算法,返回合并后的带头结点的升序序链表,其头结点为L3。结点的结构定义如下:
typedef struct ListNode {
    int val;
    struct ListNode* next;
} ListNode;
要求:(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></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>0(1)</td><td rowspan=1 colspan=1>13</td></tr></table> ## 【解法1】枚举法 (1)枚举L2中的每个结点,将其插入到L1中正确的位置,始终保持L1的升序特性。每次插入时从L1头部开始线性查找插入位置。(2)代码如下:
//查找并插入p
{ void Find_Insert(ListNode *pre, ListNode *p)
    while (pre-&gt;next!=NULL &amp;&amp; pre-&gt;next-&gt;val&lt;p-&gt;val) // 查找插入位置
    pre=pre-&gt;next;
    p-&gt;next=pre-&gt;next;
    pre-&gt;next=p;
}
ListNode* ans(ListNode *L1, ListNode *L2)
{
    ListNode* p=L2-&gt;next; // L2跳过头结点
    while (p!=NULL){
        ListNode* temp=p-&gt;next; // temp暂存下一个结点
        Find_Insert(L1, p);
        p=temp;
    }
    return L1;
}
<!-- page 126 --> (3)时间复杂度:O(mn)空间复杂度:O(1) 【解法2】使用多指针法,利用链表的升序特性合并。 (1)同时遍历两个链表,逐个比较结点的值,将较小的结点尾插法插入到新链表L3中,直到其中一个链表遍历完为止。然后将未遍历完的链表的剩余部分直接连接到新链表L3的尾部,返回L3就可以得到合并后的升序序列。(2)代码如下: 培价
ListNode* ans(ListNode *L1, ListNode *L2)
{
    ListNode*L3=(ListNode*)malloc(sizeof(ListNode));// L3作为新链表的起点
    L3-&gt;next=NULL;
    L1=L1-&gt;next; // L1跳过头结点
    L2=L2-&gt;next; // L2跳过头结点
    ListNode* p=L3;
    while (L1!=NULL &amp;&amp; L2!=NULL){
        if (L1-&gt;val&lt;=L2-&gt;val){
            p-&gt;next=L1;
            L1=L1-&gt;next;
        }
        else{ p-&gt;next=L2;
            L2=L2-&gt;next;
        }
        p=p-&gt;next;
    }
    /*将链表剩余部分直接链接到新链表L3的尾部*/
    if (L1!=NULL)
    p-&gt;next=L1;
    else
    return L3; p-&gt;next=L2;
}
(3)时间复杂度:O(m+ n) 空间复杂度:O(1) ## 习题3-2 (13分)已知两个长度分别为m和n带头结点的升序链表L1和L2,若将它们合并为一个长度为m + n的降序链表,头结点为L3。请设计一个时间和空间上尽可能高效的算法,返回合并后的 结点的结构定义如下: <!-- page 127 -->
typedef struct ListNode {
    int val;
    struct ListNode* next;
} ListNode;
要求:(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((m + n)2)</td><td rowspan=1 colspan=1>O(m + n)</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>0(1)</td><td rowspan=1 colspan=1>13</td></tr></table> 本题的枚举法暴力解和上一题类似,不再给出 ## 【解法1】枚举法+转数组 (1)首先将链表L2的结点依次取下来,按照升序查找并插入到链表L1中得到升序链表。然后将升序链表L1转入数组中,再将数组逆置,然后存回链表中。(2)代码如下:
// 查找并插入p
void Find_Insert(ListNode *pre, ListNode *p)
{
    while (pre-&gt;next!=NULL &amp;&amp; pre-&gt;next-&gt;val&lt;p-&gt;val) //查找插入位置
    pre=pre-&gt;next;
    p-&gt;next=pre-&gt;next;
    pre-&gt;next=p; //将p插入到pre之后
}
ListNode* ans(ListNode *L1,ListNode *L2)
{
    ListNode* p=L2-&gt;next; // L2跳过头结点
    while (p!=NULL)
    {
        ListNode* temp=p-&gt;next;
        Find_Insert(L1, p);
        p=temp;
        p=L1-&gt;next;
        int len=0;
        int a[max_nm];
        while (p!=NULL)
        {
            a[len]=p-&gt;val;
            len++;
            p=p-&gt;next;
        }
        p=L1-&gt;next;
        for (int i=len-1;i&gt;=0;i--) // 逆序写回链表
        {
            p-&gt;val=a[i];
            p=p-&gt;next;
        }
        return L1;
    }
}
<!-- page 128 --> (3)时间复杂度:O((m + n)2) 空间复杂度:O(m + n) 【解法2】多指针法+头插法:利用链表的升序特性合并,插入时使用头插法得到降序序列。 (1)采用归并思想,每次比较L1与L2当前元素值,将较小的结点直接头插到新链表L3中。头插法本身天然实现了逆序,无需额外逆置操作,最后即可得到降序链表。 (2) 代码如下:
ListNode* Head_Insert(ListNode* L, ListNode* p)
{
    ListNode* temp=p-&gt;next;
    p-&gt;next=L-&gt;next;
    L-&gt;next=p;
} return temp;
ListNode* Merge(ListNode *L1, ListNode *L2)
{
    ListNode* L3=(ListNode*)malloc(sizeof(ListNode)); // L3是新链表的起点
    L3-&gt;next=NULL;
    L1=L1-&gt;next; // L1跳过头结点
    L2=L2-&gt;next; // L2跳过头结点
    while (L1!=NULL &amp;&amp; L2!=NULL)
    if(L1-&gt;val&lt;=L2-&gt;val)
    L1=Head_Insert(L3, L1);
    else
    L2=Head_Insert(L3, L2);
    // 将链表剩余结点依次头插法插入新链表L3中
    while (L1!=NULL)
    L1=Head_Insert(L3, L1);
    while (L2!=NULL)
    L2=Head_Insert(L3, L2);
    return L3;
}
(3)时间复杂度:O(m + n)空间复杂度:O(1) ## 习题3-3 (13分)设计一个空间复杂度为O(1)且时间上尽可能高效的算法int IsPalindrome(linklist L),判断一个带头结点的单链表(表头指针为L,链表长度为n)除头结点外是否对称,若对称, <!-- page 129 --> 则返回1,否则返回0。例如,表(25,34,34,25)和表(25,3,25)都是对称的,表(25,3,30)不是对称的。数据类型定义如下: 研
typedef struct node{
    int data;
    struct node *next;
}node, *linklist;
(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>预计得分K</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>优化解 KH</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></td></tr></table> 本题要求空间复杂度为O(1),所以不能使用转数组的方法。 ## 【解法1】枚举法 (1)先遍历链表统计总共有多少个元素,然后两层循环依次枚举前后每一对元素比较。 (2) 代码如下:
int IsPalindrome(linklist L)
{
    if (L==NULL I|L-&gt;next==NULL) //链表为空或一个结点时
    return 1;
    linklist p=L-&gt;next;
    int len=0;
    while (p!=NULL)
    {
        len++;
        p=p-&gt;next;
        linklist left=L, right;
        for(int i=1;i&lt;=len/2;i++)
        left=left-&gt;next;
        right=L;
        for (int j=1;j&lt;=len-i;j++)
        right=right-&gt;next;
        if (left-&gt;data!=right-&gt;data)
        return 0; // 老
        return 1;
    }
}
<!-- page 130 --> ```c (3) // 时间复杂度:O(n²) ```