C 408—《数据结构》算法题基础篇—链表(下)

news2024/11/30 2:44:17

目录

Δ前言

一、两个升序链表归并为一个降序链表

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

二、两个链表的所有相同值结点生成一个新链表

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

三、两个链表的所有相同值结点放入到其中一个原链表中

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

四、判断一个链表是否为另一个链表的连续子序列

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

五、判断一个循环双链表是否对称

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

六、两个循环单链表的连接 (链接)

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

七、循环单链表反复找到最小值结点并删除

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

八、非循环双链表查改结点并按照结点访问频度进行排序

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

九、查找单链表中倒数第K个结点 [2009真题]

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

十、两个链表存储两个单词求共同后缀的起始位置 [2012真题]

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

十一、链表绝对值的去重 [2015真题]

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

十二、链表元素的重新交错排列 [2019真题]

        0.题目:

        1.算法设计思想:

        2.C语言描述:

        3.算法的时间和空间复杂度:

Δ总结


Δ前言

  • 408应试的角度来看,算法题主要分为四部分——①线性表(包括数组和链表);②二叉树;③图;④查找。本篇博文主要讲链表相关基础算法(由于链表相关的算法题目较多,所以up分为了上下两篇博文)
  • 博文中涉及到的题目全部参考自《王道数据结构考研复习指导》《竟成408考研复习全书》。
  • 注意事项——①点击文章侧边栏目录或者文章开头的目录可以进行跳转。所有题目都符合408算法题的题目格式,即第一问算法设计思想、第二问C语言描述该算法、第三问计算该算法的时空复杂度。
  • 良工不示人以朴,up所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!

一、两个升序链表归并为一个降序链表

        0.题目:

        已知有两个升序的单链表(按照结点的值递增排序),现要求将这两个升序的单链表归并为一个降序的单链表,并且要求除了原本两个升序链表的结点外,在归并过程中不允许创建新的结点。

        请设计一个算法来实现上述操作,用C语言描述该算法,并且计算出该算法的时间复杂度和空间复杂度。

        1.算法设计思想:

        我们早在“C 408—《数据结构》算法题基础篇—数组”一文中就接触过归并思想。它的算法思想是:从头到尾依次比较两个顺序表中的元素,并把更小的那个元素放在最终的顺序表中,直到合并完毕;只不过现在从"顺序表"换成"链表"了,在顺序中我们是通过三个下标实现的,那么在链表中我们自然要通过三个指针来实现了。

        p1指针指向listA链表的结点,p2指针指向listB链表的结点。利用while循环遍历两个升序链表,每次将更小的那个结点头插到新的链表中。(题干要求我们从“升序”变为“降序”,不难想到要用到头插法,因为头插法可以逆置链表)

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Structure Defining
typedef struct LinkNode {
    int data;
    struct LinkNode * next;
} LinkNode, * LinkList;

//Functions Declaration
void initialize(LinkList * pHead);
LinkNode * createNewNode(int data);
void traverse(LinkList pHead);
void freeList(LinkList pHead);
void freeSingleNode(LinkNode * node);
LinkList mergeTwoList(LinkList listA, LinkList listB);

int main(void) {
    //给定两个特定的升序链表,用于测试该算法。
    LinkList listA,listB;
    initialize(&listA);
    initialize(&listB);

    LinkNode * a1 = createNewNode(1);
    LinkNode * a2 = createNewNode(2);
    LinkNode * a3 = createNewNode(2);
    LinkNode * a4 = createNewNode(3);
    LinkNode * a5 = createNewNode(4);
    LinkNode * a6 = createNewNode(5);
    listA->next = a1;
    a1->next = a2;
    a2->next = a3;
    a3->next = a4;
    a4->next = a5;
    a5->next = a6;
    a6->next = NULL;

    LinkNode * b1 = createNewNode(11);
    LinkNode * b2 = createNewNode(77);
    LinkNode * b3 = createNewNode(141);
    listB->next = b1;
    b1->next = b2;
    b2->next = b3;
    b3->next = NULL;

    printf("\n在合并前,链表listA情况:");
    traverse(listA);

    printf("在合并前,链表listB情况:");
    traverse(listB);

    LinkList finalList = mergeTwoList(listA, listB);
    printf("在合并后,降序链表finalList情况 —— ");
    traverse(finalList);
    printf("\n");

    freeList(finalList);
    freeSingleNode(listA);
    freeSingleNode(listB);

    return 0;
}

void initialize(LinkList * pHead) {
    (*pHead) = (LinkNode * ) malloc(sizeof(LinkNode));
    (*pHead)->data = 0;
    (*pHead)->next = NULL;
}

LinkNode * createNewNode(int data) {
    LinkNode * p = (LinkNode * ) malloc(sizeof(LinkNode));
    p->data = data;
    p->next = NULL;

    return p;
}

void traverse(LinkList pHead) {
    LinkNode * p = pHead->next;

    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }

    printf("\n");
}

void freeList(LinkList pHead) {
    LinkNode * p = pHead;
    LinkNode * temp;
    
    while (p != NULL) {
        temp = p;
        p = p->next;
        free(temp);
    }
}

void freeSingleNode(LinkNode * node) {
    free(node);
}

LinkList mergeTwoList(LinkList listA, LinkList listB) {
    LinkList finalList;
    initialize(&finalList); 

    LinkNode * p1 = listA->next;
    LinkNode * p2 = listB->next;
    LinkNode * temp;                        //定义temp指针是为了防止在头插的时候断链。

    while (p1 != NULL && p2 != NULL) {      //只要链表listA和listB中还有元素没有遍历完毕,就继续进行合并。
        if (p1->data <= p2->data) {         //注意——目标链表是降序链表,所以要先插小的。
            temp = p1->next;
            //进行 头插
            p1->next = finalList->next;
            finalList->next = p1;
            //恢复p1指针
            p1 = temp;
        } else {
            temp = p2->next;

            p2->next = finalList->next;
            finalList->next = p2;

            p2 = temp;
        }
    }

    while (p1 != NULL) {            //若listA中还有剩余的元素,就逐个头插到finalList中。
        temp = p1->next;
        p1->next = finalList->next;
        finalList->next = p1;

        p1 = temp;
    }
    
    while (p2 != NULL) {            //若listB中还有剩余的元素,就逐个头插到finalList中。
        temp = p2->next;
        p2->next = finalList->next;
        finalList->next = p2;

        p2 = temp;
    }

    return finalList;
}

                运行结果 :

        3.算法的时间和空间复杂度:

        (1) 时间复杂度 : 

        O(m + n). 这是合并两个链表的最佳时间复杂度.
        (2) 空间复杂度 : 
        O(1). 因为我们没有创建新的结点(题目也不允许我们创建), 整个过程只使用了几个临时的辅助变量, 所以该算法是"原地工作"的.


二、两个链表的所有相同值结点生成一个新链表

        0.题目:

        设listA和listB是两个带有头结点的单链表,且listA和listB都是升序链表(结点的值递增有序),现要求在不破坏原有链表结点的前提下,产生一个新链表listC,listC中保存的结点是listA和listB的相同值的结点(注意是值相同)。

        请设计一个算法来实现上述操作,用C语言描述该算法,并且计算出该算法的时间复杂度和空间复杂度。

        1.算法设计思想:

        题干中要求“不破坏原有链表结点”,说明我们在遍历过程中需要创建新的结点。

        使用while循环遍历listA和listB链表,每次循环都要比较当前listA和listB结点的值的大小(预设p,q指针分别指向listA和listB当前的结点),若值不相等,令值更小的指针后移;若值相等,就创建一个与当前值相等的结点,并将该结点插入到listC链表中,随后同时移动p,q指针后移,当p,q指针某一方为NULL时,说明对应的链表已经遍历完毕,此时结束while循环,返回listC链表。

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Structure Defining
typedef struct LinkNode {
    int data;
    struct LinkNode * next;
} LinkNode, * LinkList;


//Functions Declaration
void initialize(LinkList * pHead);
LinkNode * createNewNode(int data);
void traverse(LinkList pHead);
void freeList(LinkList pHead);
LinkList findCommonValues(LinkList listA, LinkList listB);

int main(void) {
    //给定两个特定的升序链表,用于测试该算法。
    LinkList listA,listB;
    initialize(&listA);
    initialize(&listB);

    LinkNode * a1 = createNewNode(1);
    LinkNode * a2 = createNewNode(2);
    LinkNode * a3 = createNewNode(2);
    LinkNode * a4 = createNewNode(3);
    LinkNode * a5 = createNewNode(5);
    listA->next = a1;
    a1->next = a2;
    a2->next = a3;
    a3->next = a4;
    a4->next = a5;
    a5->next = NULL;

    LinkNode * b1 = createNewNode(2);
    LinkNode * b2 = createNewNode(2);
    LinkNode * b3 = createNewNode(3);
    LinkNode * b4 = createNewNode(7);
    listB->next = b1;
    b1->next = b2;
    b2->next = b3;
    b3->next = b4;
    b4->next = NULL;

    printf("\n初始的升序链表listA情况: ");
    traverse(listA);

    printf("\n初始的升序链表listB情况: ");
    traverse(listB);

    LinkList listC = findCommonValues(listA, listB);
    printf("\n维护有listA, listB公共值结点的listC链表情况: ");
    traverse(listC);

    freeList(listA);
    freeList(listB);
    freeList(listC);

    return 0;
}

void initialize(LinkList * pHead) {
    (*pHead) = (LinkNode * ) malloc(sizeof(LinkNode));
    (*pHead)->data = 0;
    (*pHead)->next = NULL;
}

LinkNode * createNewNode(int data) {
    LinkNode * p = (LinkNode * ) malloc(sizeof(LinkNode));
    p->data = data;
    p->next = NULL;

    return p;
}

void traverse(LinkList pHead) {
    LinkNode * p = pHead->next;

    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }

    printf("\n");
}

void freeList(LinkList pHead) {
    LinkNode * p = pHead;
    LinkNode * temp;
    
    while (p != NULL) {
        temp = p;
        p = p->next;
        free(temp);
    }
}

LinkList findCommonValues(LinkList listA, LinkList listB) {
    LinkNode * p = listA->next;
    LinkNode * q = listB->next;
    LinkList listC;
    initialize(&listC);
    LinkNode * rear = listC;                //listC链表的尾指针,用于实现共同值结点尾插到listC链表中。

    while (p != NULL && q != NULL) {
        if (p->data < q->data) {
            p = p->next;
        } else if (p->data > q->data) {
            q = q->next;
        } else {
            //去重(Eliminates consecutive duplicates in the result list)
            if (rear == listC || p->data != rear->data) {
                LinkNode * commonValueNode = createNewNode(p->data);
                rear->next = commonValueNode;
                rear = commonValueNode;
            }

            p = p->next;
            q = q->next;
        }
    }
    /*
        由于createNewNode方法中已经将新创建的结点的next指针域置空,所以最后不需要再处理尾链。
    */
    return listC;
}

                运行结果 : 

        3.算法的时间和空间复杂度:

        (1) 时间复杂度
        The time complexity is O(n + m), where n and m are the lengths of listA and listB respectively. This is optimal for this problem.

        (2) 空间复杂度

        The space complexity is O(min{n, m}) in the worst case, which is also optimal.


三、两个链表的所有相同值结点放入到其中一个原链表中

        0.题目:

        已知两个升序的单链表listA和listB,要求将两个链表的所有公共值结点全部放入到单链表listA中。        

        请设计一个算法来实现上述操作,用C语言描述该算法,并且计算出该算法的时间复杂度和空间复杂度。

        1.算法设计思想:

        此题要与上一题类比进行理解,上一题不允许我们破坏原有结点,所以我们需要创建新的结点来生成最终的链表,这就导致算法的空间复杂度更高了,达不到算法”原地工作“的效果。而此题中,要求我们用原来的链表listA维护所有的公共值结点,所以肯定涉及到了链表的删除,就需要我们在上一题的基础上做一些小小的改动。

        仍然利用while循环遍历listA和listB链表,对于listA链表,使用两个指针currentA和prevA分别指向listA链表的当前结点和前一个结点每次循环都要比较当前listA和listB结点的值的大小若值不相等,令值更小的指针后移,如果值更小的结点是listA链表中的结点,就删除该结点,并令指向listA结点的两个指针都后移一位,如果值更小的结点是listB链表中的结点,就仅移动指向listB链表中的结点,不删除结点;若值相等,就同时移动指向listA和listB链表的指针,如果listA链表比listB链表长,那么当listB链表遍历完毕时,listA链表还没有遍历完毕,但可以肯定剩下的元素都不是公共值元素,所以还需要补一个while循环用来删除listA表中剩余的结点。

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Structure Defining
typedef struct LinkNode {
    int data;
    struct LinkNode * next;
} LinkNode, * LinkList;


//Functions Declaration
void initialize(LinkList * pHead);
LinkNode * createNewNode(int data);
void traverse(LinkList pHead);
void freeList(LinkList pHead);
void retainCommonValuesInListA(LinkList listA, LinkList listB);

int main(void) {
    //给定两个特定的升序链表,用于测试该算法。
    LinkList listA,listB;
    initialize(&listA);
    initialize(&listB);

    LinkNode * a1 = createNewNode(2);
    LinkNode * a2 = createNewNode(3);
    LinkNode * a3 = createNewNode(3);
    LinkNode * a4 = createNewNode(4);
    LinkNode * a5 = createNewNode(777);
    listA->next = a1;
    a1->next = a2;
    a2->next = a3;
    a3->next = a4;
    a4->next = a5;
    a5->next = NULL;

    LinkNode * b1 = createNewNode(1);
    LinkNode * b2 = createNewNode(3);
    LinkNode * b3 = createNewNode(4);
    LinkNode * b4 = createNewNode(11);
    listB->next = b1;
    b1->next = b2;
    b2->next = b3;
    b3->next = b4;
    b4->next = NULL;

    printf("\n初始的升序链表listA情况: ");
    traverse(listA);

    printf("\n初始的升序链表listB情况: ");
    traverse(listB);


    retainCommonValuesInListA(listA, listB);
    printf("\n最终的listA 链表情况: ");
    traverse(listA);

    freeList(listA);
    freeList(listB);

    return 0;
}

void initialize(LinkList * pHead) {
    (*pHead) = (LinkNode * ) malloc(sizeof(LinkNode));
    (*pHead)->data = 0;
    (*pHead)->next = NULL;
}

LinkNode * createNewNode(int data) {
    LinkNode * p = (LinkNode * ) malloc(sizeof(LinkNode));
    p->data = data;
    p->next = NULL;

    return p;
}

void traverse(LinkList pHead) {
    LinkNode * p = pHead->next;

    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }

    printf("\n");
}

void freeList(LinkList pHead) {
    LinkNode * p = pHead;
    LinkNode * temp;
    
    while (p != NULL) {
        temp = p;
        p = p->next;
        free(temp);
    }
}

void retainCommonValuesInListA(LinkList listA, LinkList listB) {
    LinkNode * prevA = listA;
    LinkNode * currentA = listA->next;
    LinkNode * currentB = listB->next;

    while (currentA != NULL && currentB != NULL) {
        if (currentA->data < currentB->data) {
            prevA->next = currentA->next;
            free(currentA);
            currentA = prevA->next;
        } else if (currentA->data > currentB->data) {
            currentB = currentB->next;
        } else {
            //(Eliminates consecutive duplicates in the result list)
            if (currentA->data == prevA->data) {
                prevA->next = currentA->next;
                free(currentA);
                currentA = prevA->next;
            }   

            prevA = currentA;
            currentA = currentA->next;
            currentB = currentB->next;
        }
    }

    while (currentA != NULL) {
        prevA->next = currentA->next;
        free(currentA);
        currentA = prevA->next;
    }
}

                运行结果 : 

        3.算法的时间和空间复杂度:

        (1) 时间复杂度

        The time complexity is O(n + m), where n and m are the lengths of listA and listB respectively. This is optimal for this problem.

        (2) 空间复杂度

        The space complexity is O(1) as it operates in-place on listA.


四、判断一个链表是否为另一个链表的连续子序列

        0.题目:

        现有两个带有头结点的单链表listA和listB,要求判断listB是否为listA链表的一个连续子序列。

        请设计一个算法来实现上述操作,用C语言描述该算法,并且计算出该算法的时间复杂度和空间复杂度。

        1.算法设计思想:

  1.  “暴力法”,分别用currentA和currentB两个指针指向listA和listB的当前结点,另设指针prevA记录每一轮循环的currentA是从哪个结点开始遍历的,
  2.  listA和listB均从头开始遍历,判断当前对应的结点是否相等(指值相等),
  3.  对于第一次判断,如果不相等,令currentA指针后移一位(此时currentB指针仍然指向listB链表的首结点),重新开始判断;如果相等,就令currentA和currentB指针同时向右移,并再次判断新指向的对应的结点是否相等,
  4.  如果再次判断的结果不相等,说明之前判断相等的结点到这儿就卡住了,形不成同listB一样的连续序列,也就是说此次循环中,cuttentA从prevA记录的位置开始遍历listA是找不到和B一样的连续序列的(注意此时currentB指针已经发生了移动),
  5.  那么,出现失败的情况后,对于listA,令currentA指向prevA记录结点的下一个结点,并令currentB的指针重新回到listB的首结点,开始新的一轮循环,
  6.  如果发现任意一方的指针指向NULL时,跳出循环,此时判断currentB是否为NULL,只要currentB为NULL,说明一定在listA中找到了一个与listB相同的连续子序列。

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

//Data Structure Defining
typedef struct LinkNode {
    int data;
    struct LinkNode * next;
} LinkNode, * LinkList;

//Functions Declaration
void initialize(LinkList * pHead);
LinkNode * createNewNode(int data);
void traverse(LinkList pHead);
void freeList(LinkList pHead);
bool judgeSequentialSub(LinkList listA, LinkList listB);

int main(void) {
    //Test our algorithm
    LinkList listA,listB;
    initialize(&listA);
    initialize(&listB);

    LinkNode * a1 = createNewNode(11);
    LinkNode * a2 = createNewNode(2);
    LinkNode * a3 = createNewNode(3);
    LinkNode * a4 = createNewNode(4);
    LinkNode * a5 = createNewNode(5);
    listA->next = a1;
    a1->next = a2;
    a2->next = a3;
    a3->next = a4;
    a4->next = a5;
    a5->next = NULL;

    LinkNode * b1 = createNewNode(3);
    LinkNode * b2 = createNewNode(4);
    LinkNode * b3 = createNewNode(5);
    listB->next = b1;
    b1->next = b2;
    b2->next = b3;
    b3->next = NULL;

    printf("\n初始的listA情况: ");
    traverse(listA);

    printf("\n初始的listB情况: ");
    traverse(listB);

    bool result = judgeSequentialSub(listA, listB);
    printf("Is listB a suqential sublist from listA? %s ", result ? "Yes!" : "No!");

    freeList(listA);
    freeList(listB);

    return 0;
}

void initialize(LinkList * pHead) {
    (*pHead) = (LinkNode * ) malloc(sizeof(LinkNode));
    (*pHead)->data = 0;
    (*pHead)->next = NULL;
}

LinkNode * createNewNode(int data) {
    LinkNode * p = (LinkNode * ) malloc(sizeof(LinkNode));
    p->data = data;
    p->next = NULL;

    return p;
}

void traverse(LinkList pHead) {
    LinkNode * p = pHead->next;     //Side note: Remember the type of next;
    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }

    printf("\n");
}

void freeList(LinkList pHead) {
    LinkNode * p = pHead;
    LinkNode * temp;

    while (p != NULL) {
        temp = p;
        p = p->next;
        free(temp);
    }
}

bool judgeSequentialSub(LinkList listA, LinkList listB) {
    LinkNode * currentA = listA->next;
    LinkNode * lastTimeA = currentA;            //To record where the currentA begins its traversing
    LinkNode * currentB = listB->next;

    while (currentA != NULL && currentB != NULL) {
        if (currentA->data == currentB->data) {
            currentA = currentA->next;
            currentB = currentB->next;  
        } else {                                //Update the currentA and lastTimeA if it doesn't match
            currentA = lastTimeA->next;
            lastTimeA = currentA;

            if (listB->next != currentB) {      //If listB didn't move, we won't reset it.
                currentB = listB->next;
            }
        }
    }

    //return (currentB == NULL) ? true : false;
    return currentB == NULL; 
}

                运行结果 : 
 

        3.算法的时间和空间复杂度:

        (1) 时间复杂度

        In the worst case scenario:

        You might need to check every node in listA as a potential starting point for a match.

        For each starting point, you might need to compare with all nodes in listB.

        So the time complexity is O(n * m) , where n is the number of nodes in listA and m is the number of nodes in listB.

        (2) 空间复杂度

        Uses only a fixed number of additional pointers (currentA, lastTimeA, currentB) regardless of the input size.

        It doesn't use any additional data structures that grow with the input size.

        Therefore, the space complexity is O(1) - constant space.


五、判断一个循环双链表是否对称

        0.题目:

        有一个带有头结点的循环双链表,判断其是否为对称的链表(讨论对称不包括头结点)。

        请设计一个算法来实现上述操作,用C语言描述该算法,并且计算出该算法的时间复杂度和空间复杂度。

        1.算法设计思想:

        注意,此题与之前所有的题目都不同——此题的操作对象是双链表,而且是循环双链表,这意味着我们需要去重新定义新的数据结构,以及新的用于链表初始化的方法,和用于遍历链表,释放链表内存的方法。

        关于链表是否对称,其实就是从链表的两端开始,每一对对应的结点的值都相同,不难想到会出现链表个数为奇数和链表个数为偶数这两种情况(一般讨论链表结点数时,不考虑头结点,因为头结点并不承载有效数据),使用两个指针left 和 right分别初始化为链表的尾结点和首结点,left指针以尾结点到首结点方向从右向左遍历,right指针则从首结点开始,依次向右遍历,只要left和right指向的结点的数据域相同,就继续遍历下去

        奇数个结点和偶数个结点的差别体现在遍历什么时候结束对于奇数个结点的循环双链表,如果left和right即将指向的下一个结点是同一个结点,说明之前遍历过的对应结点都是data域相同的,到这里就该结束了;对于偶数个结点的循环双链表,如果left指向的下一个结点是right当前指向的结点,或者如果right指向的下一个结点是left当前指向的结点,说明遍历到这里也该结束了,这是一个对称的链表。反之,只要在遍历过程中发现有任何一对结点的值不对应相等,说明这不是一个对称的链表,直接return就可以了。

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

//Data Structure Defining
typedef struct DlinkNode {
    int data;
    struct DlinkNode * prev, * next;
} DlinkNode, * DlinkList;

//Functions Declaration
void initialize(DlinkList * pHead);
DlinkNode * createNewNode(int data);
void traverse(DlinkList pHead);
void freeList(DlinkList pHead);
bool judgeDoubleLink(DlinkList pHead);


int main(void) {
    //Here is one specific circular double linked list ,to test our algorithm
    DlinkList pHead;
    initialize(&pHead);

    DlinkNode * d1 = createNewNode(11);
    DlinkNode * d2 = createNewNode(5);
    DlinkNode * d3 = createNewNode(9);
    DlinkNode * d4 = createNewNode(5);
    DlinkNode * d5 = createNewNode(11);

    pHead->next = d1;
    d1->prev = pHead;

    d1->next = d2;
    d2->prev = d1;

    d2->next = d3;
    d3->prev = d2;

    d3->next = d4;
    d4->prev = d3;

    d4->next = d5;
    d5->prev = d4;

    d5->next = pHead;
    pHead->prev = d5;

    printf("\nLet take a look of our tentative list: ");
    traverse(pHead);

    bool key = judgeDoubleLink(pHead);
    printf("Is it a symmetic list? %s", key ? "Yes!" : "No!");

    freeList(pHead);

    return 0;
}

void initialize(DlinkList * pHead) {
    (*pHead) = (DlinkNode * ) malloc(sizeof(DlinkNode));
    (*pHead)->data = 0;
    (*pHead)->prev = NULL;
    (*pHead)->next = NULL;
}

DlinkNode * createNewNode(int data) {
    DlinkNode * p = (DlinkNode * ) malloc(sizeof(DlinkNode));
    p->data = data;
    p->prev = NULL;
    p->next = NULL;

    return p;
}

void traverse(DlinkList pHead) {        //Don't forget it's a circular linked list
    DlinkNode * p = pHead->next;
    
    while (p != pHead) {
        printf("%d ", p->data);
        p = p->next;
    }

    printf("\n");
}

void freeList(DlinkList pHead) {        //Don't forget it's a circular linked list
    DlinkNode * p = pHead;
    DlinkNode * temp;

    while (p->next != pHead) {
        temp = p;
        p = p->next;
        free(temp);
    }

    free(pHead);
}

bool judgeDoubleLink(DlinkList pHead) {
    if (pHead->next == pHead) return true;// Handle empty lists

    DlinkNode * left = pHead->prev;
    DlinkNode * right = pHead->next;

    while (left->data == right->data) {
        if (left->prev == right->next)      //When the number of list is odd, take this handling
            return true;
        if (left->prev == right || right->next == left) //When the number of list is even, take this handling
            return true;

        left = left->prev;
        right = right->next;
    }

    return false;
}

                运行结果 : 

        3.算法的时间和空间复杂度:

        (1) 时间复杂度

        The time complexity is O(n/2), which simplifies to O(n), where n is the number of nodes in the list.

        (2) 空间复杂度

        Therefore, the space complexity is O(1) (constant space).


六、两个循环单链表的连接 (链接)

        0.题目:

        给出两个均带有头结点的循环单链表,链表头指针分别为pHeadA and pHeadB,请设法将链表pHeadB链接到链表pHeadA的后面,要求得到的新链表仍然保持循环的特性。

        请设计一个算法来实现上述操作,用C语言描述该算法,并且计算出该算法的时间复杂度和空间复杂度。

        1.算法设计思想:

        这个没啥可讲的吧,就和链表结点的尾插类似,只不过现在是要插一个链表,把指针看清楚别瞎几把指就行。

        对了,这个题和上一个题又不一样,这个题的操作对象是循环单链表,这意味着我们得在之前单链表定义好的数据结构和相关函数的基础上,做一些改动,比方说遍历链表的函数,以及释放链表内存的函数。

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Strcture Defining
typedef struct LinkNode {
    int data;
    struct LinkNode * next;
} LinkNode, * LinkList;

//Functions Declaration
void initialize(LinkList * pHead);
LinkNode * createNewNode(int data);
void traverse(LinkList pHead);
void freeList(LinkList pHead);
void insertList(LinkList listA, LinkList listB);

int main(void) {
    //Test our algorithm
    LinkList listA,listB;
    initialize(&listA);
    initialize(&listB);

    LinkNode * a1 = createNewNode(1);
    LinkNode * a2 = createNewNode(2);
    LinkNode * a3 = createNewNode(3);
    LinkNode * a4 = createNewNode(4);
    LinkNode * a5 = createNewNode(5);
    listA->next = a1;
    a1->next = a2;
    a2->next = a3;
    a3->next = a4;
    a4->next = a5;
    a5->next = listA;

    LinkNode * b1 = createNewNode(11);
    LinkNode * b2 = createNewNode(12);
    LinkNode * b3 = createNewNode(13);
    listB->next = b1;
    b1->next = b2;
    b2->next = b3;
    b3->next = listB;

    printf("What's the content of original listA : ");
    traverse(listA);
    printf("What's the content of original listB : ");
    traverse(listB);

    insertList(listA, listB);
    printf("After insersion, FINAL listA : ");
    traverse(listA);

    freeList(listA);
    free(listB);

    return 0;
}

void initialize(LinkList * pHead) {     //No distinction with the uncircular linked list
    (*pHead) = (LinkNode * ) malloc(sizeof(LinkNode));
    (*pHead)->data = 0;
    (*pHead)->next = NULL;
}

LinkNode * createNewNode(int data) {    //No distinction with the uncircular linked list
    LinkNode * p = (LinkNode * ) malloc(sizeof(LinkNode));
    p->data = data;
    p->next = NULL;

    return p;
}

void traverse(LinkList pHead) {         //DIFFERENCE no.1
    LinkNode * p = pHead->next;     
    
    while (p != pHead) {
        printf("%d ", p->data);
        p = p->next;
    }

    printf("\n");
}

void freeList(LinkList pHead) {         //DIFFERENCE no.2
    LinkNode * p = pHead->next;
    LinkNode * temp;

    while (p != pHead) {
        temp = p;
        p = p->next;
        free(temp);
    }

    free(pHead);
}

void insertList(LinkList listA, LinkList listB) {
    LinkNode * p = listA;
    LinkNode * rearA;                   //point to the end of listA
    LinkNode * rearB;                   //point to the end of listB

    while (p->next != listA) {          //To find the rear of listA to faciliate our insersion
        p = p->next;
    }
    rearA = p;
    rearA->next = listB->next;

    while (p->next != listB) {
        p = p->next;
    }
    rearB = p;
    rearB->next = listA;
}

                运行结果 : 

        3.算法的时间和空间复杂度:

        (1) 时间复杂度

        The total time complexity is O(n + m).

        (2) 空间复杂度

        Space Complexity of insertList: O(1).


七、循环单链表反复找到最小值结点并删除

        0.题目:

        已知一个带有头结点的循环单链表,要求不断地找出链表中的最小值结点,把最小值结点的值打印出来并且删除该结点,直到该链表一个有效结点不剩,最后再删除头结点。

        请设计一个算法来实现上述操作,用C语言描述该算法,并且计算出该算法的时间复杂度和空间复杂度。

        1.算法设计思想:

        在“C—408《数据结构》算法题基础篇—链表(上)”一文中,也就是链表专题的上篇,有一道题目是删除链表的最小值结点,当时up已经较为详细地介绍了删除最小值结点需要的两个操作“找”和“删”,这里就不再赘述,如果有对思路或者相关代码有不懂的朋友们可以点击链接再去回顾一下(就在第二道题);

        而此题相当于之前那道题的升级版,主要差异在了两个地方——①本题操作的链表是循环链表②本题并不是只删除一个最小值结点,而是要逐个击破,赶尽杀绝(bushi,所以up重点说一下如何处理这两个问题:针对第一个差异,只需要注意循环链表的判空条件与非循环链表不同即可;针对第二个差异,只需要在之前那道题的基础上再套一层循环即可。

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Strcture Defining
typedef struct LinkNode {
    int data;
    struct LinkNode * next;
} LinkNode, * LinkList;

//Functions Declaration
void initialize(LinkList * pHead);
LinkNode * createNewNode(int data);
void traverse(LinkList pHead);
void freeList(LinkList pHead);
void continuouslyDetectMinimum(LinkList pHead);

int main(void) {
    //A specific circular linked list to test our algorithm
    LinkList pHead = NULL;              
    initialize(&pHead);                 

    LinkNode * a1 = createNewNode(11);
    LinkNode * a2 = createNewNode(14);
    LinkNode * a3 = createNewNode(141);
    LinkNode * a4 = createNewNode(5);
    LinkNode * a5 = createNewNode(100);

    pHead->next = a1;                  
    a1->next = a2;
    a2->next = a3;
    a3->next = a4;
    a4->next = a5;
    a5->next = pHead;

    printf("初始链表情况如下——\n");
    traverse(pHead);
    
    printf("挨个删除链表的最小值结点,删除次序分别如下:\n");
    continuouslyDetectMinimum(pHead);
    freeList(pHead);

    return 0;
}

void initialize(LinkList * pHead) {     
    (*pHead) = (LinkNode * ) malloc(sizeof(LinkNode));
    (*pHead)->data = 0;
    (*pHead)->next = NULL;
}

LinkNode * createNewNode(int data) {    
    LinkNode * p = (LinkNode * ) malloc(sizeof(LinkNode));
    p->data = data;
    p->next = NULL;

    return p;
}

void traverse(LinkList pHead) {         //DIFFERENT FROM NORMAL LINKED LIST
    LinkNode * p = pHead->next;

    while (p != pHead) {
        printf("%d ", p->data);
        p = p->next;
    }

    printf("\n");
}

void freeList(LinkList pHead) {         //DIFFERENT FROM NORMAL LINKED LIST
    LinkNode * p = pHead->next;
    LinkNode * temp;

    while (p != pHead) {
        temp = p;
        p = p->next;
        free(temp);
    }

    free(pHead);
}

void continuouslyDetectMinimum(LinkList pHead) {
    LinkNode * tempPre = pHead, * minPre = pHead;
    LinkNode * temp = pHead->next, * min = pHead->next;

    while (pHead->next != pHead) {          //Don't forget that it's a circular one
        while (temp != pHead) {
            if (temp->data < min->data) {   //move the min and minPre if condition matched
                min = temp;
                minPre = tempPre;
            }
            tempPre = temp;
            temp = temp->next;  //temp is used to traverse the list,so it should be updated at every loop.
        }

        printf("%d ", min->data);       //Demand-no.1 print the current minimum node's data
        minPre->next = min->next;       //Demand-no.2 delete the current minimum node itself
        free(min);           

        //updated all four pointer for the next-time operation(find and print and delete)
        tempPre = pHead, minPre = pHead;        
        temp = pHead->next, min = pHead->next;
    }

    printf("\n");
}

                运行结果 :

        3.算法的时间和空间复杂度:

        (1) 时间复杂度

  • The outer while loop runs until the list is empty, so it executes n times, where n is the initial number of nodes in the list.
  • For each iteration of the outer loop, the inner while loop traverses the entire remaining list to find the minimum value.
  • In the first iteration, it checks n nodes, in the second n-1, then n-2, and so on.
  • This forms an arithmetic sequence: n + (n-1) + (n-2) + ... + 2 + 1
  • The sum of this sequence is n(n+1)/2
  • Therefore, the time complexity is O(n^2)

        (2) 空间复杂度

  1. The algorithm uses a fixed number of pointers (tempPre, minPre, temp, min) regardless of the input size.
  2. No additional data structures are created that grow with the input size.
  3. The space used is constant throughout the execution of the algorithm
  4. Overall Space Complexity: O(1)

八、非循环双链表查改结点并按照结点访问频度进行排序

        0.题目:

        已知一个带有头结点的非循环双向链表,该链表的每个结点都维护有四个属性——①pev(指向当前结点的前驱结点);②data(当前结点的有效值);③freq(当前结点的访问频度,初始值为0);④next(指向当前结点的后继结点)。现定义一个Locate(pHead, data)操作,该操作会访问链表中元素值为data的结点,并将该结点的freq域的值加一,同时,还会令当前链表结点保持freq域递减的顺序进行排列,并且,当两个结点的freq域的值相同时,要求最近被访问的结点优先排在前面,以便使得频繁访问的结点总是更靠近链表头。要求定义一个函数实现上述Locate(pHead, data)操作,并且返回目标结点的指针。(假设给定的链表已经按照freq降序排序)

        请设计一个算法来实现上述操作,用C语言描述该算法,并且计算出该算法的时间复杂度和空间复杂度。

        1.算法设计思想:

        先对题干进行分解,可知该算法共需要完成五个步骤—①查找链表中指定值的结点;②修改该结点的值;③取出该结点(涉及到双链表中结点的删除);④查找到第一个freq域比该结点大的结点;⑤将该结点插入到第一个freq域比该结点大的结点的后面(涉及到双链表中结点的插入)。

        这五个操作都很基础,因此这是一道综合题,但算不上难题,每个操作本身都没难度,注意双链表结点的删除和插入时,指针给指对就行,别瞎🐔⑧乱指。

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Structure Defining(a little bit different from normal doubly linked list)
typedef struct DlinkNode {
    int data;
    int freq;
    struct DlinkNode * prev, * next;
} DlinkNode, * DlinkList;

//Functions Declaration
void initialize(DlinkList * pHead);
DlinkNode * createNewNode(int data);
void traverse(DlinkList pHead);
void freeList(DlinkList pHead);
DlinkNode * findCertainNodeAndSortByFreq(DlinkList pHead, int data);


int main(void) {
    //Here is one specific double linked list ,to test our algorithm
    DlinkList pHead;
    initialize(&pHead);

    DlinkNode * d1 = createNewNode(11);
    DlinkNode * d2 = createNewNode(5);
    DlinkNode * d3 = createNewNode(2);
    DlinkNode * d4 = createNewNode(141);
    DlinkNode * d5 = createNewNode(77);

    pHead->prev = NULL;
    pHead->next = d1;
    d1->prev = pHead;

    d1->next = d2;
    d2->prev = d1;

    d2->next = d3;
    d3->prev = d2;

    d3->next = d4;
    d4->prev = d3;

    d4->next = d5;
    d5->prev = d4;

    d5->next = NULL;

    printf("At the original phase, the doubly linked list like this: ");
    traverse(pHead);

    DlinkNode * aim = findCertainNodeAndSortByFreq(pHead, 77);
    printf("The aim node's value = %d, and its freq = %d ", aim->data, aim->freq);
    printf("\nAfter we find the 77-value node, the list is like: ");
    traverse(pHead);

    freeList(pHead);

    return 0;
}

void initialize(DlinkList * pHead) {
    (*pHead) = (DlinkNode * ) malloc(sizeof(DlinkNode));
    (*pHead)->data = 0;
    (*pHead)->freq = 0;         //Notice this extra field.
    (*pHead)->prev = NULL;
    (*pHead)->next = NULL;
}

DlinkNode * createNewNode(int data) {
    DlinkNode * p = (DlinkNode * ) malloc(sizeof(DlinkNode));
    p->data = data;
    p->freq = 0;
    p->prev = NULL;
    p->next = NULL;

    return p;
}

void traverse(DlinkList pHead) {        
    DlinkNode * p = pHead->next;
    
    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }

    printf("\n");
}

void freeList(DlinkList pHead) {       
    DlinkNode * p = pHead;          //The head node also need to be freeed at end.
    DlinkNode * temp;

    while (p != NULL) {
        temp = p;
        p = p->next;
        free(temp);
    }
}

DlinkNode * findCertainNodeAndSortByFreq(DlinkList pHead, int data) {
    DlinkNode * aim = NULL;                     //to point to the aim node
    DlinkNode * traversePointer = pHead->next;  //to traverse the doubly linked lists
    DlinkNode * insert_pos = pHead;             //to identify the position for aim to insert

    //Step no.1 --- find the aim node
    while (traversePointer != NULL && traversePointer->data != data) {
        traversePointer = traversePointer->next;
    }
    aim = traversePointer;
    if (aim == NULL) {          //NOT FOUND
        return NULL;
    }

    //Step no.2 ---  modify the freq field of aim node
    (aim->freq)++;

    //Step no.3 --- withdraw the aim node
    traversePointer->prev->next = traversePointer->next;
    if (aim->next != NULL) 
        traversePointer->next->prev = traversePointer->prev;

    //Step no.4 --- find the first previous node which hold a bigger freq than aim node
    while (insert_pos->next != NULL && insert_pos->next->freq > aim->freq) {
        insert_pos = insert_pos->next;
    }
    
    //Step no.5 --- insert aim node into the right position found just hereinbefore
    aim->next = insert_pos->next;
    insert_pos->next->prev = aim;
    insert_pos->next = aim;
    aim->prev = insert_pos;

    return aim;
}

                运行结果:

        3.算法的时间和空间复杂度:

        (1) 时间复杂度

  1. Finding the target node:

    • In the worst case, we might need to traverse the entire list.
    • This takes O(n) time, where n is the number of nodes in the list.
  2. Update the target node:
    • It takes only O(1) time.
  3. Removing the node:

    • This is a constant time operation, O(1).
  4. Finding the correct position to insert:

    • In the worst case, we might need to traverse the entire list again.
    • This also takes O(n) time.
  5. Inserting the node:

    • This is a constant time operation, O(1).

        Overall Time Complexity: O(n)

        (2) 空间复杂度

  • The function uses a fixed number of pointers (aim, insert_pos) regardless of the input size.
  • No additional data structures are created that grow with the input size.
  • The space used is constant throughout the execution of the function.

        Overall Space Complexity: O(1)


九、查找单链表中倒数第K个结点 [2009真题]

        0.题目:

        已知一个带有表头结点的单链表,且结点的数据结构为一个data数据域和一个link指针域,假设该链表只给出头指针list,在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表的倒数第K个位置上的结点(K为正整数)。若查找成功,算法输出该结点data域的值,并返回1;否则,只返回0。要求:

        1)描述算法的基本设计思想。

        2)描述算法的详细实现步骤。

        3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C、或者C++实现)关键之处给出简要注释。

        1.算法设计思想:

        设计思想:

        由于链表倒数第K个结点到链表尾的距离是一定的,即只需移动K-1次就可以到达链表尾;所以,可以先从链表头开始向后遍历,移动K-1次就可以找到链表正着数的第K个结点,而从链表头到正着数的第K个结点的距离,与从倒数第K个结点到链表尾的距离是相等的,所以,只需要让指向第一段距离的前后两个指针同时后移,当后一个指针指向表尾时,前一个指针就指向了倒数第K个结点,而这一段距离是没有变化的

        详细实现步骤:

        考虑使用“双指针”法,首先令两个指针同时指向表头结点,先令其中一个指针向后移动K-1次,该指针即指向了链表的正着数的第K个结点,然后令前后两个指针同时后移,当后一个指针指向NULL(表尾)时,前一个指针即指向了链表倒数第K个结点。

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Structure Defining
typedef struct LinkNode {
    int data;
    struct LinkNode * link;       //The name of pointer field is to conform to a certain topic.
} LinkNode, * LinkList;

//Functions Declaration
void initialize(LinkList * pHead);
LinkNode * createNewNode(int data);
void traverse(LinkList pHead);
void freeList(LinkList pHead);
int findTheKthNodeFromTheEnd(LinkList list, int K);    //The name "K" and "list" is to adapt topic.

int main(void) {
    //Given a specifc linkd list, Test our algorithm
    LinkList list;
    initialize(&list);

    LinkNode * a1 = createNewNode(11);
    LinkNode * a2 = createNewNode(5);
    LinkNode * a3 = createNewNode(2);
    LinkNode * a4 = createNewNode(77);
    LinkNode * a5 = createNewNode(141);
    LinkNode * a6 = createNewNode(211);
    LinkNode * a7 = createNewNode(9);

    list->link = a1;
    a1->link = a2;
    a2->link = a3;
    a3->link = a4;
    a4->link = a5;
    a5->link = a6;
    a6->link = a7;
    a7->link = NULL;

    printf("Let's see what's the original list looks like: ");
    traverse(list);

    printf("So what is the third-to-last element? \n");
    int key = findTheKthNodeFromTheEnd(list, 3);
    printf("Did we find it? %s ", (key == 1) ? "Yes!" : "No!");

    freeList(list);

    return 0;
}

void initialize(LinkList * pHead) {
    (*pHead) = (LinkNode * ) malloc(sizeof(LinkNode));
    (*pHead)->data = 0;
    (*pHead)->link = NULL;
}

LinkNode * createNewNode(int data) {
    LinkNode * p = (LinkNode * ) malloc(sizeof(LinkNode));
    p->data = data;
    p->link = NULL;

    return p;
}

void traverse(LinkList pHead) {
    LinkNode * p = pHead->link;     //Side note: Remember the type of link;
    while (p != NULL) {
        printf("%d ", p->data);
        p = p->link;
    }

    printf("\n");
}

void freeList(LinkList pHead) {
    LinkNode * p = pHead;
    LinkNode * temp;

    while (p != NULL) {
        temp = p;
        p = p->link;
        free(temp);
    }
}

int findTheKthNodeFromTheEnd(LinkList list, int K) {
    if (K <= 0) return 0;                       //Handle the first illegal occasion.(too small K)

    LinkNode * former = list, * latter = list;  //Both pointer is initially pointing the head node.s

    for (int count = 0; count < K; ++count) {
        if (latter != NULL)                     //Handle the second illegal occasion.(too big K)
            latter = latter->link;
        else 
            return 0;
    }

    while (latter != NULL) {
        former = former->link;
        latter = latter->link;
    }

    int aimData = former->data;
    printf("value = %d", aimData);
    printf("\n");

    return 1;
}

                运行结果:

        3.算法的时间和空间复杂度:

        (1) 时间复杂度 : 
        O(n), where n is the number of nodes in the list. In the worst case, we need to traverse the list once.
        (2) 空间复杂度 : 

        O(1), as we only use a constant amount of extra space regardless of the input size.


十、两个链表存储两个单词求共同后缀的起始位置 [2012真题]

        0.题目:

        假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,则可共享相同的后缀存储空间,例如,"loading"和"being"的存储映像如下图所示:

        设str1和str2分别指向两个单词所在单链表的头结点,链表结点结构为一个data数据域和一个next指针域。请设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后缀的起始位置。要求:

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

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

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

        1.算法设计思想:

        如果两个链表的长度相同,那么我们只需要用两个指针同时遍历两个链表,当两个指针指向同一个结点时(注意是指针相同),说明找到了共同后缀的起始位置;

        但是更一般地,由于两个单词的长度往往不相同,所以对应地两个链表的长度也不会相同,这时候需要先得到两个链表的长度之差,然后让较长的链表先遍历前面长度之差个结点,这时候两个指针又到了同一起跑线了(即两个指针到共同后缀起始位置的距离是一样的),同时令两个指针后移即可找到共同后缀的起始位置。
        PS : 其实这道题和我们在链表上篇中做过的第七题一样。(当时讲了两种方法,感兴趣的朋友们可以去看看)

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Structure Defining
typedef struct LinkNode {
    char data;
    struct LinkNode * next;
} LinkNode, * LinkList;

//Functions Declaration
void initialize(LinkList * pHead);
LinkNode * createNewNode(char data);
void traverse(LinkList pHead);
int getLength(LinkList pHead);
void freeList(LinkList pHead);
LinkNode * findTheStartOfCommonSuffix(LinkList str1, LinkList str2); 

int main(void) {
    //Given a specific one to test our algorithm
    LinkList str1;
    LinkList str2;
    initialize(&str1);
    initialize(&str2);

    //define nodes exclusive to str1
    LinkNode * str1_1 = createNewNode('l');
    LinkNode * str1_2 = createNewNode('o');
    LinkNode * str1_3 = createNewNode('a');
    LinkNode * str1_4 = createNewNode('d');
    //define nodes exclusive to str2
    LinkNode * str2_1 = createNewNode('b');
    LinkNode * str2_2 = createNewNode('e');

    //define some common nodes
    LinkNode * common1 = createNewNode('i');
    LinkNode * common2 = createNewNode('n');
    LinkNode * common3 = createNewNode('g');

    //connect these nodes as we want
    str1->next = str1_1;
    str1_1->next = str1_2;
    str1_2->next = str1_3;
    str1_3->next = str1_4;
    str1_4->next = common1;
    common1->next = common2;
    common2->next = common3;
    common3->next = NULL;

    str2->next = str2_1;
    str2_1->next = str2_2;
    str2_2->next = common1;

    printf("Let's see the original list of str1 : ");
    traverse(str1);

    printf("Let's see the original list of str2 : ");
    traverse(str2);

    LinkNode * firstNodeOfCommonSuffix = findTheStartOfCommonSuffix(str1, str2);


    printf("The value of the first common node of common suffix is :%c", firstNodeOfCommonSuffix->data);
    freeList(str1);
    freeList(str2);

    return 0;
}

void initialize(LinkList * pHead) {
    (*pHead) = (LinkNode * ) malloc(sizeof(LinkNode));
    (*pHead)->data = '0';
    (*pHead)->next = NULL;
}

LinkNode * createNewNode(char data) {
    LinkNode * p = (LinkNode * ) malloc(sizeof(LinkNode));
    p->data = data;
    p->next = NULL;

    return p;
}

void traverse(LinkList pHead) {
    LinkNode * p = pHead->next;

    while (p != NULL) {
        printf("%c ",p->data);
        p = p->next;
    }

    printf("\n");
}

int getLength(LinkList pHead) {
    LinkNode * p = pHead->next;
    int count = 0;

    while (p != NULL) {         //LOOP
        count++;
        p = p->next;
    }

    return count;
}

void freeList(LinkList pHead) {
    LinkNode * p = pHead;
    LinkNode * temp = NULL;

    while (p != NULL) {
        temp = p;
        p = p->next;
        free(temp);
    }
}

LinkNode * findTheStartOfCommonSuffix(LinkList str1, LinkList str2) {
    LinkNode * temp = NULL;
    int variance = getLength(str1) - getLength(str2);

    //keep the str1 pointer is always pointing to the longer one.
    if (variance < 0) {
        temp = str1;
        str1 = str2;
        str2 = temp;
    }

    for (int i = 0; i < variance; ++i) {
        str1 = str1->next;
    }


    //Make sure we will begin to compare from the first effective node.(Keep consistency)
    str1 = str1->next;
    str2 = str2->next;

    while (str1 != NULL) {
        if (str1 == str2) {
            return str1;
        } else {
            str1 = str1->next;
            str2 = str2->next;
        }
    }

    return NULL;
}

                运行结果:

        3.算法的时间和空间复杂度:

        (1) 时间复杂度 : 

        findTheStartOfCommonSuffix function:

  • It calls getLength twice, once for each list: O(n + m), where n and m are the lengths of the two lists.
  • It then traverses the longer list from the beginning to the potential start of the common suffix: O(max(n, m))
  • Finally, it compares the nodes of both lists until it finds the common node or reaches the end: O(min(n, m))

        Overall time complexity: O(n + m)
        (2) 空间复杂度

        The space complexity is O(1) (constant space). The algorithm uses a fixed amount of extra space regardless of the input size.


十一、链表绝对值的去重 [2015真题]

        0.题目:

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

        要求:

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

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

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

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

        1.算法设计思想:

        题干中提到“时间复杂度尽可能高效”,却没有要求空间复杂度,其实就是在提醒我们,要考虑“以空间换时间”了。

        考虑如何利用辅助空间,使得链表中每一个元素都能与之对应,并且可以很快查找到已保存的元素。不难想到顺序表的”随机存取“的特点,因此,可以将链表结点的|data|值转化为用数组的下标表示,由于链表结点|data| ≤ n,所以要申请一片大小为n + 1的连续空间(确保链表中每一个结点的|data|都可以用数组的下标来记录,因为|data| = n的下标对应了第n + 1个元素),数组所有元素全部初始化为0(0表示该下标对应的绝对值的结点还没有被保存);遍历链表,如果是第一次扫描到该绝对值的结点,就把数组对应下标的元素设为1(1表示该下标对应的绝对值的结点已经被保存),那么当下一次扫描到相同绝对值的结点时,通过数组下标对应的1就可以检测到该绝对值的结点已经存在了,此时需要删除当前结点。

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Structure Defining
typedef struct LinkNode {
    int data;
    struct LinkNode * next;
} LinkNode, * LinkList;

//Functions Declaration
void initialize(LinkList * pHead);
LinkNode * createNewNode(int data);
void traverse(LinkList pHead);
void freeList(LinkList pHead);
void removeAbsoluteValueDuplicate(LinkList pHead, int n);

int main(void) {
    //Given a specific one to test our algorithm
    LinkList HEAD;
    initialize(&HEAD);

    LinkNode * a1 = createNewNode(5);
    LinkNode * a2 = createNewNode(11);
    LinkNode * a3 = createNewNode(-11);
    LinkNode * a4 = createNewNode(-5);
    LinkNode * a5 = createNewNode(77);

    HEAD->next = a1;
    a1->next = a2;
    a2->next = a3;
    a3->next = a4;
    a4->next = a5;
    a5->next = NULL;

    printf("The original list looks like : ");
    traverse(HEAD);

    removeAbsoluteValueDuplicate(HEAD, 77);
    printf("After remove abs-value duplicate, it looks like: ");
    traverse(HEAD);

    freeList(HEAD);

    return 0;
}

void initialize(LinkList * pHead) {
    (*pHead) = (LinkNode * ) malloc(sizeof(LinkNode));
    (*pHead)->data = 0;
    (*pHead)->next = NULL;
}

LinkNode * createNewNode(int data) {
    LinkNode * p = (LinkNode * ) malloc(sizeof(LinkNode));
    p->data = data;
    p->next = NULL;

    return p;
}

void traverse(LinkList pHead) {
    LinkNode * p = pHead->next;

    while (p != NULL) {
        printf("%d ",p->data);
        p = p->next;
    }

    printf("\n");
}

void freeList(LinkList pHead) {
    LinkNode * p = pHead;
    LinkNode * temp = NULL;

    while (p != NULL) {
        temp = p;
        p = p->next;
        free(temp);
    }
}

void removeAbsoluteValueDuplicate(LinkList pHead, int n) {
    LinkNode * pre = pHead;
    LinkNode * current = pHead->next;

    //Define a auxiliary space(use index to refer to node's absolute-data)
    int * recordArray = (int * ) malloc(sizeof(int) * (n + 1)); 

    for (int i = 0; i < n + 1; ++i) {       //all elements is initialized given the value of 0
        recordArray[i] = 0;                 //The value 0 means that corresponding-data node is not yet been preserved.
    }

    while (current != NULL) {
        if (recordArray[abs(current->data)] == 0) {
            recordArray[abs(current->data)] = 1;        //distinguish between the "=" and the "==".
            pre = current;
            current = current->next;
        } else if (recordArray[abs(current->data)] == 1) {
            pre->next = current->next;
            free(current);
            current = pre->next;
        }
    }

    free(recordArray);
}

                运行结果:

        3.算法的时间和空间复杂度:

        (1) 时间复杂度
        O(m), where m is the length of the list, as we traverse the list once.
        (2) 空间复杂度

        O(n), where n is the maximum absolute value, due to the auxiliary array.


十二、链表元素的重新交错排列 [2019真题]

        0.题目:

        设线性表L = (a1, a2, a3, ..., an-2, an-1, an) 采用带头结点的单链表保存,链表中的结点定义如下:
        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)说明你所设计的算法的时间复杂度。

        1.算法设计思想:

        (a1, a2, a3, ..., an-2, an-1, an)要想变成(a1, an, a2, an-1, a3, an-2, ...),不难看出就是依次从表头和表尾开始挨个选元素,表头选一个表尾选一个,依次类推,直到将链表中的元素重新排列成一个新的序列;通过观察我们可知,我们的目标序列按照奇偶位序可以划分为两个子序列,分别为(a1, a2, a3, ..., amid) 和 (an, an-1, an-2, ..., amid+1),第二个子序列恰好就是原来链表后半部分的逆置,如果能得到后半部分序列的逆置,再和第一个子序列交替进行尾插,即得到目标序列。那么第一个问题就是如何得到后半部分序列的逆置?不难想到要先拿到链表中间元素amid的指针,然后对后半部分进行头插即可逆置。
        在上面第九题“寻找链表的倒数第K个结点”时,我们使用了”双指针”法,只不过当时我们是保持两个指针之间的距离不变(保持了K的距离),这样当前面的指针遍历到表尾时,后面的指针就指向了倒数第K个结点。

        这道题我们要用到双指针法的一个特殊情况——“快慢指针”法,见名知意,也就是说前后两个指针在遍历过程中不再保持固定的距离了,而是一前一后两个指针移动的速度并不一样快

        为什么要提到快慢指针法?用快慢指针是为了找到链表的中间结点。为什么要找到链表的中间结点?找到中间结点是为了将链表的后半部分进行逆置。针对于本题,只需要让快指针每次移动两个结点,慢指针每次移动一个结点,当快指针指到达链表尾时,慢指针指向的即是链表的中间结点。

        所以,本题可以分解为三个子问题——①得到链表的中间结点的指针;②将链表后半部分的结点进行逆置;③链表前半部分和已经逆置的后半部分分别交替进行尾插即可得到目标序列

        2.C语言描述:

                完整代码如下:(注意看注释)

#include <stdio.h>
#include <stdlib.h>

//Data Structure Defining
typedef struct node {
    int data;
    struct node * next;
} NODE; //The name is to correspond the topic.

//Functions Declaration
void initialize(NODE ** pHead);
NODE * createNewNode(int data);
void traverse(NODE * pHead);
int getLength(NODE * pHead);
void freeList(NODE * pHead);
NODE * reArrangeList(NODE * pHead);

int main(void) {
    //Given a specifc list to test our algorithm
    NODE * pHead;
    initialize(&pHead);

    NODE * a1 = createNewNode(1);
    NODE * a2 = createNewNode(3);
    NODE * a3 = createNewNode(5);
    NODE * a4 = createNewNode(-11);
    NODE * a5 = createNewNode(6);
    NODE * a6 = createNewNode(4);
    NODE * a7 = createNewNode(2);

    pHead->next = a1;
    a1->next = a2;
    a2->next = a3;
    a3->next = a4;
    a4->next = a5;
    a5->next = a6;
    a6->next = a7;
    a7->next = NULL;

    printf("Let's see the original list: ");
    traverse(pHead);
    NODE * resultList = reArrangeList(pHead);
    printf("So the re-arranged list looks like: ");
    traverse(resultList);

    freeList(resultList);
    free(pHead);

    return 0;
}

void initialize(NODE ** pHead) {
    (*pHead) = (NODE * ) malloc(sizeof(NODE));
    (*pHead)->data = 0;
    (*pHead)->next = NULL;
}

NODE * createNewNode(int data) {
    NODE * p = (NODE * ) malloc(sizeof(NODE));
    p->data = data;
    p->next = NULL;

    return p;
}

void traverse(NODE * pHead) {
    NODE * p = pHead->next;

    while (p != NULL) {
        printf("%d ",p->data);
        p = p->next;
    }

    printf("\n");
}

int getLength(NODE * pHead) {
    NODE * p = pHead->next;
    int count = 0;

    while (p != NULL) {         
        count++;
        p = p->next;
    }

    return count;
}

void freeList(NODE *  pHead) {
    NODE * p = pHead;
    NODE * temp = NULL;

    while (p != NULL) {
        temp = p;
        p = p->next;
        free(temp);
    }
}

NODE * reArrangeList(NODE * pHead) {
    //if the number of list less than 3, there is no need to take this algorithm
    if (pHead == NULL || pHead->next == NULL || pHead->next->next == NULL || pHead->next->next->next == NULL)
        return pHead;

    //Fields For Step 1
    NODE * fasterP = pHead->next;
    NODE * slowerP = pHead->next;       //this pointer is used to point the mid node of the list.
    NODE * prev = NULL;
    //Fields For Step 2
    NODE * head = (NODE * ) malloc(sizeof(NODE));//it will be the head node of rear half of the list.
    NODE * temp = NULL;                 //help to reverse the rear half with head-insersion method.
    NODE * tempNext = NULL;
    int length = 0;
    //Fields For Step 3
    NODE * tempNodeContainer = (NODE * ) malloc(sizeof(NODE));
    NODE * rear = tempNodeContainer;    //!!! the rear is used to fufill tail-insersion in Step 3
    NODE * fore = pHead->next;          //Same as the previous one.

    //Step 1 : get the pointer of mid node
    while (fasterP != NULL && fasterP->next != NULL) {
        fasterP = fasterP->next->next;
        prev = slowerP;
        slowerP = slowerP->next;
    }
    /*!!!!!!!  --- Mark This:
        Now the slowerP is at middle (or just after middle for even length lists),
        and the prev is at the previous node next to slowerP.
    */

    //Step 2 : reverse the rear half of the list(use the head-insersion method)
        /*
            split the list into two halves:
            the nodes supposed to be reversed will be left in the rear half, others stay in fore half.
        */
    length = getLength(pHead);
    if (length / 2 == 0) {          //When it's an even number, slowerP is just after middle
        head->next = slowerP;
        prev->next = NULL;
    } else {                        //When it's an odd number, slowerP is at middle
        head->next = slowerP->next;
        slowerP->next = NULL;
    }
    temp = head->next;              //the first effective node of the rear half
    tempNext = temp->next;

    while (tempNext != NULL) {
        temp->next = tempNext->next;      //avert the fracture of rear half
        tempNext->next = head->next;
        head->next = tempNext;

        tempNext = temp->next;            //also successfully handle the last node's next field.
    }
    temp = head->next;                    //prepare to execute Step 3


    //Step 3 : insert the node from fore half and the node from rear half(already reversed) respectively
    while (fore != NULL && temp != NULL) {
        rear->next = fore;
        rear = fore;
        fore = fore->next;

        rear->next = temp;
        rear = temp;
        temp = temp->next;   
    }
    if (fore != NULL) {                   //fore half can only have one more node than rear half
        rear->next = fore;
        rear = fore;
    }

    rear->next = NULL;
    pHead->next = tempNodeContainer->next;
    free(tempNodeContainer);

    return pHead;
}   

                运行结果:

        3.算法的时间和空间复杂度:

        (1) 时间复杂度

  1. Finding the middle of the list (Step 1):

    • This uses the fast and slow pointer technique, which traverses the list once.
    • Time complexity: O(n), where n is the number of nodes in the list.
  2. Reversing the rear half of the list (Step 2):

    • This step traverses approximately half of the list once.
    • Time complexity: O(n/2), which simplifies to O(n).
  3. Merging the two halves (Step 3):

    • This step iterates through all nodes once.
    • Time complexity: O(n).

        Overall Time Complexity: O(n) + O(n) + O(n) = O(n)

        (2) 空间复杂度

  1. We create a constant number of additional nodes:

    • One dummy node for the rear half (head).
    • One temporary node (tempNodeContainer).
    • A few pointer variables (temp, tempNext, rear, fore, etc.).
  2. All these additional space requirements are constant and do not grow with the input size.

        Overall Space Complexity: O(1)


Δ总结

  • 算法题基础篇——链表,上下两篇博文共计22道题目,除了最后一道题有点难度外基本考的都是基本功,需要我们对链表有扎实的理解。
  • 大家回顾一下,一开始我们接触了头插法尾插法,后来又碰到了单链表的逆置,再到后面单链表的逆置只是作为了整个算法的一部分实现,整个过程是循序渐进的,希望大家可以多练习多回顾,举一反三。
  • 之后可能还会给大家分享“树”和“图”相关的代码题,敬请期待。
  • 🆗,以上就是本篇博文的全部内容了,感谢阅读!

        System.out.println("END------------------------------------------");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2218438.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DDD重构-实体与限界上下文重构

DDD重构-实体与限界上下文重构 概述 DDD 方法需要不同类型的类元素&#xff0c;例如实体或值对象&#xff0c;并且几乎所有这些类元素都可以看作是常规的 Java 类。它们的总体结构是 Name: 类的唯一名称 Properties&#xff1a;属性 Methods: 控制变量的变化和添加行为 一…

MySQL中 truncate、drop和delete的区别

MySQL中 truncate、drop和delete区别 truncate 执行速度快&#xff0c;删除所有数据&#xff0c;但是保留表结构不记录日志事务不安全&#xff0c;不能回滚可重置自增主键计数器 drop 执行速度较快&#xff0c;删除整张表数据和结构不记录日志事务不安全&#xff0c;不能回…

JavaWeb——Maven(3/8):配置Maven环境(当前工程,全局),创建Maven项目

目录 配置Maven环境 当前工程 全局 创建Maven项目 配置Maven环境 当前工程 选择 IDEA中 File --> Settings --> Build,Execution,Deployment --> Build Tools --> Maven 设置 IDEA 使用本地安装的 Maven&#xff0c;并修改配置文件及本地仓库路径 首先在 IDE…

QtCreator14调试Qt5.15出现 Launching Debugger 错误

1、问题描述 使用QtCreator14调试程序&#xff0c;Launching Debugger 显示红色&#xff0c;无法进入调试模式。 故障现象如下&#xff1a; 使能Debugger Log窗口&#xff0c;显示&#xff1a; 325^error,msg"Error while executing Python code." 不过&#xff…

【软件推荐】信创终端上通过kshutdown实现定时关机 _ 统信 _ 麒麟 _ 方德

往期好文&#xff1a;【功能介绍】麒麟2403支持配置任务栏上的图标“从不合并”啦&#xff01; Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于如何在信创终端系统上通过kshutdown实现定时关机的文章。在日常使用中&#xff0c;定时关机是一个非常实用的功能&am…

Leetcode 841. 钥匙和房间

1.题目基本信息 1.1.题目描述 有 n 个房间&#xff0c;房间按从 0 到 n – 1 编号。最初&#xff0c;除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而&#xff0c;你不能在没有获得钥匙的时候进入锁住的房间。 当你进入一个房间&#xff0c;你可能会在…

uniapp 微信小程序分包操作

1. 在项目根目录创建一个新的目录&#xff0c;名称为分包名称 2. 打开manifest.json&#xff0c;选择源码视图&#xff0c;加入以下代码 "optimization" : {"subPackages" : true } 3. 在pages.json中&#xff0c;pages后面添加分包代码 "subPackag…

tkinter Listbox 列表框实现多列对齐排列并绑定下拉框和滚动条

from tkinter import * from tkinter import ttk, Button, Canvas, Listbox, Entry, LabelFrame, IntVar, Checkbutton, messageboximport win32print root Tk() root.title("tkinter Listbox 列表框实现多列对齐排列") root.geometry(550x450)def callback2(t, eve…

k8s-对命名空间资源配额

对k8s命名空间限制的方法有很多种&#xff0c;今天来演示一下很常用的一种 用的k8s对象就是ResourceQuota 一&#xff1a;创建命名空间 kubectl create ns test #namespace命名空间可以简写成ns 二&#xff1a; 对命名空间进行限制 创建resourcequota vim resourcequ…

Http请求转发服务器实现

Http请求转发服务器实现 需求场景 云服务器通过VPN连接了现场的n台工控机&#xff0c;每台工控机上都在跑web程序&#xff0c;现在我想通过公网直接访问工控机上的web服务&#xff0c;给客户查看现场的具体运行情况&#xff0c;而不是让客户再装一个VPN&#xff0c;简化操作。…

Axure重要元件三——中继器函数

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;中继器函数 主要内容&#xff1a;Item、Reperter、TargetItem 1、中继器的函数&#xff1a;Item\Reperter\TargetItem Item item&#xff1a;获取…

【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第五篇-着色器投影-投射阴影部分】

投射阴影 最初打算将投影内容放在上一篇中&#xff0c;因为实现非常快速简单&#xff0c;没必要单独成篇。不过因为这里面涉及一些问题&#xff0c;我觉得还是单独作为一篇讲一下比较好。 原理 这里要用到的是 Shadow Pass Switch ,它可以为非不透明的材质替换阴影 某些版本…

ubuntu22.04下GStreamer源码编译单步调试

前言 本文会通过介绍在linux平台下的GStreamer的源码编译和单步调试example实例。官网介绍直接通过命令行来安装gstreamer可以参考链接&#xff1a;Installing on Linux。 这种方法安装后&#xff0c;基于gstreamer的程序&#xff0c;单步调试的时候并不会进入到gstreamer源码…

架构师:Nginx 实现负载均衡的技术指南

1、简述 NGINX 是一种高性能的 HTTP 服务器和反向代理服务器,广泛应用于 web 服务器场景中。负载均衡是 NGINX 的重要功能之一,能够将请求分发到多个服务器上,提高应用的可用性和性能。 NGINX 负载均衡的主要策略: 轮询 (Round Robin): 将请求依次分发到后端服务器,每个…

中国移动机器人将投入养老场景;华为与APUS共筑AI医疗多场景应用

AgeTech News 一周行业大事件 华为与APUS合作&#xff0c;共筑AI医疗多场景应用 中国移动展出人形机器人&#xff0c;预计投入养老等场景 作为科技与奥富能签约&#xff0c;共拓智能适老化改造领域 天与养老与香港科技园&#xff0c;共探智慧养老新模式 中山大学合作中国…

Web应用程序的设计与前端开发

我们的客户专门从事自动化系统的开发和支持&#xff0c;用于分析、报告、规划和其他业务任务&#xff0c;以及集成外部产品。 任务 我们的客户开始开发一个用于企业业务分析的web应用程序。他们自己处理后端&#xff0c;而我们的团队负责界面和前端。界面不仅在视觉上具有吸引…

论文阅读(十六):Deep Residual Learning for Image Recognition

文章目录 1.介绍2.基本原理3.两种残差块4.网络结构 论文&#xff1a;Deep Residual Learning for Image Recognition   论文链接&#xff1a;Deep Residual Learning for Image Recognition   代码链接&#xff1a;Github 1.介绍 在ResNet网络提出之前&#xff0c;传统的卷…

医疗领域的RAG技术:如何通过知识图谱提升准确性

在医学领域&#xff0c;准确的信息检索和处理至关重要。随着大型语言模型&#xff08;LLMs&#xff09;的兴起&#xff0c;检索增强生成&#xff08;RAG&#xff09;技术在医学信息处理中的应用越来越受到关注。本文将探讨RAG技术在医学领域的应用&#xff0c;特别是如何利用知…

动态规划 - 完全背包问题

文章目录 题目描述题解思路题解代码 题目描述 题解思路 完全背包问题和01背包问题不同的地方就是完全背包问题中每个物品能选无数次&#xff0c;而01背包问题中每个物品最多只能选择一次 如果你还没有学过01背包&#xff0c;请先看这篇博客学习01背包&#xff1a;https://blo…

基于Javaweb的医院挂号预约管理系统

系统展示 用户前台界面 管理员后台界面 医生后台界面 系统背景 在现代社会&#xff0c;随着医疗需求的不断增长&#xff0c;病患挂号成为医院面临的一大挑战。传统的挂号方式不仅耗时耗力&#xff0c;还容易引发混乱和不满。病患需要排队等候&#xff0c;挂号过程繁琐&#xff…