3.6 章节练习

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

## 3.6 章节练习

## 3.6.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)说明你所设计算法的时间复杂度和空间复杂度。 ## 【习题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)说明你所设计算法的时间复杂度和空间复杂度。 ## 【习题3-3】 (13分)设计一个空间复杂度为O(1)且时间上尽可能高效的算法int IsPalindrome(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 84 -->
原资料配图
原资料配图 · 转写内容请回源核验
原资料配图
原资料配图 · 转写内容请回源核验

## 【习题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, 链表结构如下:

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

(1)写出链表结点的数据类型定义

(2)采用C或C++语言描述算法,关键之处给出注释。

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

## 【习题3-5】

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

typedef struct node{
    int data;
    struct node *next;
}node, *LinkList;
(1)给出算法的基本设计思想。 (2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。 (3)说明所设计算法的时间复杂度。 ## 3.6.2 真题 【2009年42题】 (15分)已知一个带有表头结点的单链表,结点结构为 <table><tr><td>data</td><td>link</td></tr></table> 假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1;否则,只返回0。要求: (1)描述算法的基本设计思想。 (2)描述算法的详细实现步骤。 <!-- page 85 --> (3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C、C++或Java语言实现),关键之处请给出简要注释。 ka ## 【2012年42题】 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)说明你所设计算法的时间复杂度。

## 【2015年41题】

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

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

则删除结点后的 HEAD为

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

要求:

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

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

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

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

## 【2019年41题】

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

<!-- page 86 -->

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)说明你所设计的算法的时间复杂度。 ## 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>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】枚举法 (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;
        while (p!=NULL){
            ListNode* temp=p-&gt;next;
            Find_Insert(L1, p);
            p=temp;
        }
        return L1;
    }
}
<!-- page 87 -->
(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)
    om
    p-&gt;next=L1;
    else
    p-&gt;next=L2;
    return L3;
}
(3) // 时间复杂度: O(m + n) 空间复杂度:O(1)
<!-- page 88 --> ## 习题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)说明你所设计算法的时间复杂度和空间复杂度。 <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)
{
    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; // temp暂存下一个结点
        Find_Insert(L1, p);
        p=temp;
        道汁
        p=L1-&gt;next;
        int len=0;
        int a[max_nm]; // max_nm是m+n之和的最大值
        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 89 -->
(3) // 时间复杂度:O((m + n)²) 空间复杂度: 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-&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)
<!-- page 90 --> ## 习题3-3 (13分)设计一个空间复杂度为O(1)且时间上尽可能高效的算法int IsPalindrome(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)说明所设计算法的时间复杂度。 <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>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)先遍历链表统计总共有多少个元素,然后两层循环依次枚举前后每一对元素比较。 (2) 代码如下:
int IsPalindrome(linklist L)
{
    if (L==NULL || 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 91 --> ```c (3) // 时间复杂度:O(n²) ```