链表练习题1:移除链表指定元素、反转链表、合并两个有序序列、链表分割、链表的回文结构、相交链表

目录

一、移除链表指定元素

做题的两种思路:

1.思路1:把原链表中不是val的结点尾插到新链表中并把原链表中是val的结点删除掉。

1.1.思路1的两种实现方式:

(1)利用不带哨兵位头结点不循环的单链表实现

(2)利用带哨兵位头结点不循环的单链表实现

 2.思路2:

图形解析

代码

3.测试代码

二、反转链表

1.思路1:把原链表的结点一个一个取下来头插到新链表中,得到的新链表就是反转链表。

图形解析

代码

2.思路2:把原链表的所有结点的成员变量next都指向上一个结点即逆序所有结点的指向。当原链表所有结点的指向逆序完后得到的链表就是反转链表。

图形解析

代码

3.测试代码

三、合并两个有序序列

1.思路1

注意事项

2.思路1的两种写法

(1)写法1:不带哨兵位头结点不循环单链表

图形解析

代码

(2)写法2:带哨兵位头结点不循环单链表

代码

3.测试代码

四、链表分割

1.思路1步骤

(1)步骤1

(2)步骤2

2.该题必须要处理的三种场景(以图形进行分析)

(1)场景1:原链表中的数据有比x大的,也有比x小的。

(2)场景2:原链表中的所有数据都比x大。

(3)场景3:原链表中的所有数据都比x小。

3.代码

4.测试代码

五、链表的回文结构

1.单链表回文结构的定义

2.解题思路

2.1思路1

2.2.思路2:

2.3思路3

3.思路1的实现

3.1.思路1的思路

3.2.该题必须解决的两种场景

3.3.代码实现

图形解析

代码

六、相交链表

1.对相交链表的理解

1.1.相交链表的定义

1.2.相交链表的图形结构

相交单链表的实际图形

小结

2.判断两条链表是否有相同结点的思路

3.判断两条链表是否是相交链表,若是则返回第一个交点的地址的思路

3.1.解题思路

(1)思路1——先判断是否是相交链表,再找出第一个交点

(2)思路2 —— 暴力查找:判断两条链表是否相交的同时再找出第一个交点

(3)思路3:利用带头双向循环链表判断是否是相交链表并找出第一个交点

3.2.思路1的代码实现


一、移除链表指定元素

题目链接:. - 力扣(LeetCode)

做题的两种思路:

1.思路1:把原链表中不是val的结点尾插到新链表中并把原链表中是val的结点删除掉。

1.1.思路1的两种实现方式:
(1)利用不带哨兵位头结点不循环的单链表实现

图形解析:

代码: 

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

//链表结点的结构体类型
struct ListNode
{
    int val;
    struct ListNode* next;
};


//注意:下面方法1与方法2的思路都是把原链表中不是val的结点尾插到新链表中并把原链表中是val的结点删除掉。只是方法1与方法2的实现方式不一样。

//方法1:不带哨兵位头结点的尾插
 struct ListNode* removeElements(struct ListNode* head, int val) 
 {
     //判断是否是空链表
     if(head == NULL)
     return NULL;
     //定义临时指针cur遍历整个原链表
     struct ListNode* cur = head;
     //创建新链表并把新链表初始化为空链表
     struct ListNode* newhead = NULL;
     struct ListNode* tail = NULL;//指针tail指向新链表的尾结点
     //遍历原链表
     while(cur)
     {
         //找原链表当前cur位置的下一个结点
         struct ListNode*next = cur->next;
         //把原链表中不是val的元素取下来尾插到新链表中
         if(cur->val != val)
         {
             //判断新链表是不是头插
             if(newhead == NULL)
             {
                 newhead = tail = cur;
             }
             else//尾插
             {
                 tail->next = cur;
                 tail = tail->next;//移动到新链表的下一个结点
             }
             cur = next;//移动到原链表的下一个结点
         }
         else
         {
             free(cur);
             cur = next;//移动到原链表的下一个结点
         }

     }
     //把新链表的尾结点置为空指针
     if(tail)//这个if语句的作用是为防止原链表都是要删除的元素会使得新链表的尾指针tail为空指针进而防止对空指针进行解引用。
     tail->next = NULL;
     return newhead;
 }
(2)利用带哨兵位头结点不循环的单链表实现

图形解析:

代码: 

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

//链表结点的结构体类型
struct ListNode
{
    int val;
    struct ListNode* next;
};

//方法2:带哨兵位头结点的尾插
//注意:利用新链表是带哨兵位头结点的单链表实现该题的好处是不用考虑新链表是否是头插。
 struct ListNode* removeElements(struct ListNode* head, int val) 
 {
     //判断是否是空链表
     if(head == NULL)
     return NULL;

     struct ListNode* cur = head;
     struct ListNode* guard = NULL,*tail = NULL;//指针guard指向新链表的哨兵位头结点,指针tail指向新链表的尾结点
     //创建哨兵位头结点
     struct ListNode* tmp = (struct ListNode*)malloc(sizeof(struct ListNode));
     if(tmp == NULL)
     {
         perror("malloc fail");
         exit(-1);
     }
     guard = tail = tmp;
     //对哨兵位头结点进行初始化
     guard->next = NULL;

     //遍历原链表
     while(cur)
     {
         //找原链表当前cur位置的下一个结点
         struct ListNode*next = cur->next;
         //把原链表中不是val的元素取下来尾插到新链表中
         if(cur->val != val)
         {
             //注意:这里不用考虑新链表是否是头插的情况,因为新链表是个带哨兵位头结点的单链表使得新链表的头指针guard不可能是空链表。
             //尾插
             tail->next = cur;
             tail = tail->next;//移动到新链表的下一个结点
             cur = next;//移动到原链表的下一个结点
         }
         else
         {
             free(cur);
             cur = next;//移动到原链表的下一个结点
         }

     }

     //由于新链表是带有哨兵位头结点的,即使原链表都是要删除的元素但新链表尾指针tail都不可能为空指针,所以不用判断指针tail是否是空指针来防止对空指针解引用。
     tail->next = NULL;//把新链表的尾结点置为空指针
     //找新链表的第一个有效结点
     struct ListNode* first = guard->next;
     //删除新链表的哨兵位头结点
     free(guard);
     return first;
 }

 2.思路2:

利用两指针prev、cur在原链表中删除是val的结点。当cur遇到是val的结点的时候要想删除原链表cur位置的结点的话,就要先找到原链表cur位置的上一个结点的地址prev并让cur位置的上一个结点与cur位置的下一个结点链接起来后再删除cur位置是val的结点,删除后cur往后走1步;若cur没有遇到是val的结点,则cur与prev都往后走一步,直到cur遇到是val的结点才会是prev停留在cur上一个结点的位置。

图形解析

代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//链表结点的结构体类型
struct ListNode
{
    int val;
    struct ListNode* next;
};

//方法3:利用两指针prev、cur在原链表中删除是val的结点。
struct ListNode* removeElements(struct ListNode* head, int val)
{
    //判断链表是否为空链表
    if (head == NULL)
        return NULL;
    struct ListNode* prev, * cur;//指针prev永远指向cur位置的前一个不是val的结点。
    prev = cur = head;

    //遍历原链表
    while (cur)
    {
        //判断是否是头删
        if(head->val == val)//或者写成if((cur == head) && (cur->val == val))
        {
            //换头
            head = cur->next;
            //头删
            free(cur);
            cur = prev = head;//让cur、prev移动到原链表的下一个结点
        }
        else//不是头删,中间删除或者尾删
        {
            if ((cur != NULL) && (cur->val != val))
            {
                prev = cur;//让prev移动到当前cur位置
                cur = cur->next;//让cur移动到原链表的下一个结点
            }
            else if (cur->val == val)
            {
                //找cur位置的下一个结点
                struct ListNode* next = cur->next;
                //把当前cur位置的上一个结点与当前cur位置的下一个结点链接起来
                prev->next = next;
                //删除当前结点
                free(cur);
                cur = next;//让cur移动到原链表的下一个结点
            }
        }

    }
    //注意:除了原链表所有元素都是要删除的值这一情况外,其他情况都是当while循环结束后最终prev指向原链表的尾结点。
    //把原链表的尾结点的成员变量next设置为空指针
    if (prev)//若原链表的所有元素都是要删除的值则prev最终的值是空指针NULL,为了防止这一情况就要判断prev是否为空指针以此避免对空指针进行解引用
        prev->next = NULL;
    return head;
}

3.测试代码

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

//链表结点的结构体类型
struct ListNode
{
    int val;
    struct ListNode* next;
};


//注意:下面方法1与方法2的思路都是把原链表中不是val的结点尾插到新链表中并把原链表中是val的结点删除掉。只是方法1与方法2的实现方式不一样。

//方法1:不带哨兵位头结点的尾插
 struct ListNode* removeElements(struct ListNode* head, int val) 
 {
     //判断是否是空链表
     if(head == NULL)
     return NULL;
     //定义临时指针cur遍历整个原链表
     struct ListNode* cur = head;
     //创建新链表并把新链表初始化为空链表
     struct ListNode* newhead = NULL;
     struct ListNode* tail = NULL;//指针tail指向新链表的尾结点
     //遍历原链表
     while(cur)
     {
         //找原链表当前cur位置的下一个结点
         struct ListNode*next = cur->next;
         //把原链表中不是val的元素取下来尾插到新链表中
         if(cur->val != val)
         {
             //判断新链表是不是头插
             if(newhead == NULL)
             {
                 newhead = tail = cur;
             }
             else//尾插
             {
                 tail->next = cur;
                 tail = tail->next;//移动到新链表的下一个结点
             }
             cur = next;//移动到原链表的下一个结点
         }
         else
         {
             free(cur);
             cur = next;//移动到原链表的下一个结点
         }

     }
     //把新链表的尾结点置为空指针
     if(tail)//这个if语句的作用是为防止原链表都是要删除的元素会使得新链表的尾指针tail为空指针进而防止对空指针进行解引用。
     tail->next = NULL;
     return newhead;
 }

//方法2:带哨兵位头结点的尾插
//注意:利用新链表是带哨兵位头结点的单链表实现该题的好处是不用考虑新链表是否是头插。
 //struct ListNode* removeElements(struct ListNode* head, int val) 
 //{
 //    //判断是否是空链表
 //    if(head == NULL)
 //    return NULL;

 //    struct ListNode* cur = head;
 //    struct ListNode* guard = NULL,*tail = NULL;//指针guard指向新链表的哨兵位头结点,指针tail指向新链表的尾结点
 //    //创建哨兵位头结点
 //    struct ListNode* tmp = (struct ListNode*)malloc(sizeof(struct ListNode));
 //    if(tmp == NULL)
 //    {
 //        perror("malloc fail");
 //        exit(-1);
 //    }
 //    guard = tail = tmp;
 //    //对哨兵位头结点进行初始化
 //    guard->next = NULL;

 //    //遍历原链表
 //    while(cur)
 //    {
 //        //找原链表当前cur位置的下一个结点
 //        struct ListNode*next = cur->next;
 //        //把原链表中不是val的元素取下来尾插到新链表中
 //        if(cur->val != val)
 //        {
 //            //注意:这里不用考虑新链表是否是头插的情况,因为新链表是个带哨兵位头结点的单链表使得新链表的头指针guard不可能是空链表。
 //            //尾插
 //            tail->next = cur;
 //            tail = tail->next;//移动到新链表的下一个结点
 //            cur = next;//移动到原链表的下一个结点
 //        }
 //        else
 //        {
 //            free(cur);
 //            cur = next;//移动到原链表的下一个结点
 //        }

 //    }

 //    //由于新链表是带有哨兵位头结点的,即使原链表都是要删除的元素但新链表尾指针tail都不可能为空指针,所以不用判断指针tail是否是空指针来防止对空指针解引用。
 //    tail->next = NULL;//把新链表的尾结点置为空指针
 //    //找新链表的第一个有效结点
 //    struct ListNode* first = guard->next;
 //    //删除新链表的哨兵位头结点
 //    free(guard);
 //    return first;
 //}


//方法3:利用两指针prev、cur在原链表中删除是val的结点。
//struct ListNode* removeElements(struct ListNode* head, int val)
//{
//    //判断链表是否为空链表
//    if (head == NULL)
//        return NULL;
//    struct ListNode* prev, * cur;//指针prev永远指向cur位置的前一个不是val的结点。
//    prev = cur = head;
//
//    //遍历原链表
//    while (cur)
//    {
//        //判断是否是头删
//        if(head->val == val)//或者写成if((cur == head) && (cur->val == val))
//        {
//            //换头
//            head = cur->next;
//            //头删
//            free(cur);
//            cur = prev = head;//让cur、prev移动到原链表的下一个结点
//        }
//        else//不是头删,中间删除或者尾删
//        {
//            if ((cur != NULL) && (cur->val != val))
//            {
//                prev = cur;//让prev移动到当前cur位置
//                cur = cur->next;//让cur移动到原链表的下一个结点
//            }
//            else if (cur->val == val)
//            {
//                //找cur位置的下一个结点
//                struct ListNode* next = cur->next;
//                //把当前cur位置的上一个结点与当前cur位置的下一个结点链接起来
//                prev->next = next;
//                //删除当前结点
//                free(cur);
//                cur = next;//让cur移动到原链表的下一个结点
//            }
//        }
//
//    }
//    //注意:除了原链表所有元素都是要删除的值这一情况外,其他情况都是当while循环结束后最终prev指向原链表的尾结点。
//    //把原链表的尾结点的成员变量next设置为空指针
//    if (prev)//若原链表的所有元素都是要删除的值则prev最终的值是空指针NULL,为了防止这一情况就要判断prev是否为空指针以此避免对空指针进行解引用
//        prev->next = NULL;
//    return head;
//}

struct ListNode* CreateList(int* a, int n)
{
	struct ListNode* phead = NULL, * ptail = NULL;
	int x = 0;
	for (int i = 0; i < n; ++i)
	{
        //创建1个结点
		struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
		if (newnode == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		else
		{
            //对新创建结点的成员进行初始化
			newnode->val = a[i];
			newnode->next = NULL;
		}

        //通过不断进行尾插来创建n个结点的链表。
        //尾插1个结点的过程:
		if (phead == NULL)
		{
            //判断指针phead指向的不带头不循环单链表是否为空链表
			ptail = phead = newnode;//若指针phead指向链表是个空链表话,则让指针phead指向新创建的结点进而使得phead指向链表的头结点
		}
		else
		{
            //尾插
			ptail->next = newnode;
			ptail = newnode;
		}
	}

	return phead;
}

//链表打印函数
void ListPrint(struct ListNode* phead)
{
    assert(phead);
    struct ListNode* cur = phead;
    while (cur)
    {
        printf("%d->", cur->val);
        cur = cur->next;
    }
    printf("NULL\n");
}

//test函数是用来测试removeElements函数的
void test()
{
    //测试用例
    int a[] = { 1,2,6,3,4,5,6 };//要删除的值val = 6
    //int a[] = { 7,7,7,7 };//要删除的值val = 7
    //int a[] = { 6,2,6,3,4,5,6 };//要删除的值val = 6
    //int a[] = { 6,6,6,3,4,6,6 };//要删除的值val = 6

    struct ListNode* plist = CreateList(a, sizeof(a) / sizeof(int));
    plist = removeElements(plist, 6);
    ListPrint(plist);//注意:当测试的链表全是要删除的元素时打印函数在打印链表是会报错,这个报错并不是打印函数这个代码有什么问题,而是链表被removeElements函数删除成空链表了。
}

int main()
{
   //测试removeElements函数
    test();

	return 0;
}

二、反转链表

. - 力扣(LeetCode)

1.思路1:把原链表的结点一个一个取下来头插到新链表中,得到的新链表就是反转链表。

图形解析

代码
//链表存放的数据类型
typedef int Listdata;

//链表结点的结构体类型
 struct ListNode 
 {
     Listdata val;
     struct ListNode *next;
 };

 //方法1:头插法->从头遍历原链表并把原链表的所有结点一个一个的取下来头插到新链表中
 struct ListNode* reverseList(struct ListNode* head)
 {
     //判断指针head指向的链表是否为空链表
     if (head == NULL)
         return NULL;
     struct ListNode* cur = head;//指针cur用来遍历原链表
     struct ListNode* rhead = NULL;//指针rhead指向新链表的头结点

     //遍历原链表
     while (cur)
     {
         //注意:不带头不循环单链表的头插是不用考虑rhead == NULL的。
         //找当前cur位置的下一个结点
         struct ListNode* next = cur->next;
         //从原链表中取下一个结点头插到rhead指向的新链表中。
         cur->next = rhead;
         //头插完成后,要更新rhead的指向使得rhead指向新的头结点。
         rhead = cur;//换头
         //让cur移动到原链表的下一个结点
         cur = next;
     }

     return rhead;
 }

2.思路2:把原链表的所有结点的成员变量next都指向上一个结点即逆序所有结点的指向。当原链表所有结点的指向逆序完后得到的链表就是反转链表。

注意:指针n1永远指向原链表n2位置的上一个结点,同时指针n2是用来遍历整个链表而且指针n2指向当前需要逆序的结点(即指针n2指向当前需要改变指向的结点),所以指针n1与指针n2是用来反转原链表中每个结点的指向的。而指针n3是永远存放原链表n2位置的下一个结点的地址,n3的作用是让指针n2移动到原链表下一个结点的位置。

图形解析

代码
//链表存放的数据类型
typedef int Listdata;

//链表结点的结构体类型
 struct ListNode 
 {
     Listdata val;
     struct ListNode *next;
 };

//方法2:三指针n1、n2、n3
 struct ListNode* reverseList(struct ListNode* head)
 {
     //判断指针head指向的链表是否为空链表
     if (head == NULL)
         return NULL;

     struct ListNode* n1, * n2, * n3;
     n1 = NULL;//n1指向原链表n2位置的上一个结点
     n2 = head;//n2指向原链表的当前结点。用指针n2遍历整个链表。
     n3 = n2->next;//n3指向原链表n2位置的下一个结点
     while (n2)
     {
         //反转
         n2->next = n1;//利用n2指向n1来达到反转原链表的一个结点

         //移动
         n1 = n2;//n1移动到n2的位置
         n2 = n3;//n2移动到n3的位置
         if (n3)//当n2指向原链表的尾结点时,此时n3的值为空指针NULL,为了防止对空指针n3进行解引用则必须加一个if语句判断n3是否是空指针。
             n3 = n3->next;//n3移动到下一个结点的位置
     }

     return n1;//当while(n2)循环结束时,此时n2的值为空指针NULL,而n1指向反转链表的头结点。
 }

3.测试代码

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

//链表存放的数据类型
typedef int Listdata;

//链表结点的结构体类型
 struct ListNode 
 {
     Listdata val;
     struct ListNode *next;
 };

 //创建结点
 struct ListNode* BuySLTNode(Listdata x)
 {
     struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
     if (node == NULL)
     {
         perror("malloc fail");
         exit(-1);
     }

     //对新创建结点的成员进行初始化
     node->val = x;
     node->next = NULL;

     return node;
 }

//创建n个结点的链表
 struct ListNode* CreateSList(Listdata*arr,int n)
 {
     struct ListNode* phead, * tail;//phead指向新创建链表的头结点、tail指向新创建链表的尾结点
     phead = tail = NULL;
     int i = 0;
     for (i = 0; i < n; i++)
     {
         //创建新结点
         struct ListNode* newnode = BuySLTNode(arr[i]);
         //判断phead是否是空链表
         if (phead == NULL)
         {
             phead = tail = newnode;
         }
         else//尾插
         {
             //把尾结点与新创建的结点链接起来
             tail->next = newnode;
             //换尾
             tail = tail->next;//让tail移动到新的尾结点,即此时新创建的结点就是新的尾结点。
         }
     }
     return phead;
 }

 //链表打印函数
 void ListPrint(struct ListNode* phead)
 {
     assert(phead);
     struct ListNode* cur = phead;
     while (cur)
     {
         printf("%d->", cur->val);
         cur = cur->next;
     }
     printf("NULL\n");
 }

 //方法1:头插法->从头遍历原链表并把原链表的所有结点一个一个的取下来头插到新链表中
 struct ListNode* reverseList(struct ListNode* head)
 {
     //判断指针head指向的链表是否为空链表
     if (head == NULL)
         return NULL;
     struct ListNode* cur = head;//指针cur用来遍历原链表
     struct ListNode* rhead = NULL;//指针rhead指向新链表的头结点

     //遍历原链表
     while (cur)
     {
         //注意:不带头不循环单链表的头插是不用考虑rhead == NULL的。
         //找当前cur位置的下一个结点
         struct ListNode* next = cur->next;
         //从原链表中取下一个结点头插到rhead指向的新链表中。
         cur->next = rhead;
         //头插完成后,要更新rhead的指向使得rhead指向新的头结点。
         rhead = cur;//换头
         //让cur移动到原链表的下一个结点
         cur = next;
     }

     return rhead;
 }

 //方法2:三指针n1、n2、n3
 //struct ListNode* reverseList(struct ListNode* head)
 //{
 //    //判断指针head指向的链表是否为空链表
 //    if (head == NULL)
 //        return NULL;

 //    struct ListNode* n1, * n2, * n3;
 //    n1 = NULL;//n1指向原链表n2位置的上一个结点
 //    n2 = head;//n2指向原链表的当前结点。用指针n2遍历整个链表。
 //    n3 = n2->next;//n3指向原链表n2位置的下一个结点
 //    while (n2)
 //    {
 //        //反转
 //        n2->next = n1;//利用n2指向n1来达到反转原链表的一个结点

 //        //移动
 //        n1 = n2;//n1移动到n2的位置
 //        n2 = n3;//n2移动到n3的位置
 //        if (n3)//当n2指向原链表的尾结点时,此时n3的值为空指针NULL,为了防止对空指针n3进行解引用则必须加一个if语句判断n3是否是空指针。
 //            n3 = n3->next;//n3移动到下一个结点的位置
 //    }

 //    return n1;//当while(n2)循环结束时,此时n2的值为空指针NULL,而n1指向反转链表的头结点。
 //}

 void test()
 {
     //测试用例:
     Listdata arr[] = { 1,2,3,4,5 };
     //Listdata arr[] = { 1,2,3,4,1 };
     //Listdata arr[] = { 1,2 };

     //创建n个结点的链表
     struct ListNode* plist = CreateSList(arr, sizeof(arr) / sizeof(Listdata));

     //创建空链表
     //struct ListNode* plist = NULL;//这个也是测试用例

     //反转链表
     plist = reverseList(plist);

     //打印链表
     ListPrint(plist);
 }

 int main()
 {
     test();
     return 0;
 }

三、合并两个有序序列

. - 力扣(LeetCode)

1.思路1

从两个链表的表头开始依次比较传入的两个链表的结点的大小,并将两个链表中较小的结点尾插到新链表的后面即可。完成一次尾插后,接着比较未尾插的结点,并将较小的结点继续尾插到新链表后面,直到最后两个链表的结点都被尾插到新链表的后面。

注意事项

①在尾插过程中,若某一链表已被遍历完毕,则直接将另一个未遍历完的链表剩下的结点尾插到新链表后面即可。
②函数返回的时候,不是返回头结点的地址,而是第一个结点的地址,所以我们要返回头结点指向的位置并将头结点释放。

2.思路1的两种写法

(1)写法1:不带哨兵位头结点不循环单链表

图形解析

代码
//链表存放的数据类型
typedef int Listdata;

//链表结点的结构体类型
struct ListNode
{
    Listdata val;
    struct ListNode* next;
};

//代码1:不带哨兵位头结点的写法
 struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
 {
     //判断list1与list2是否是空链表的情况
     if(list1 == NULL)
     return list2;//即使list2 = NULL,这种if语句的写法也可以表示list1与list2都是空链表的情况。
     if(list2 == NULL)
     return list1;

     struct ListNode*head , *tail;//指针head指向新链表的头结点、指针tail指向新链表的尾结点。
     head = tail = NULL;

     //遍历两个链表
     while(list1 && list2)//若list1与list2其中一个为空指针就遍历结束。
     {
         //取小的尾插到新链表中
         if(list1->val < list2->val)
         {
             //判断指针head指向的新链表是否是空链表。
             if(head == NULL)
             {
                 head = tail = list1;
             }
             else
             {
                 //链接
                 tail->next = list1;
                 //换尾
                 tail = tail->next;
             }

             list1 = list1->next;//移动到链表1的下一个结点
         }
         else
         {
             //判断指针head指向的新链表是否是空链表。
             if(head == NULL)
             {
                 head = tail = list2;  
             }
             else
             {
                 //链接
                 tail->next = list2;
                 //换尾
                 tail = tail->next;
             }

             list2 = list2->next; //移动到链表2的下一个结点
         }
     }

     //在尾插过程中,若某一链表已被遍历完毕,则直接将另一个未遍历完的链表剩下的结点尾插到新链表后面即可。
     if(!list1)
     tail->next = list2;
     if(!list2)
     tail->next = list1;

     return head;
 }

(2)写法2:带哨兵位头结点不循环单链表

代码
//链表存放的数据类型
typedef int Listdata;

//链表结点的结构体类型
struct ListNode
{
    Listdata val;
    struct ListNode* next;
};

//代码2:带哨兵位头结点的写法
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* tmp = (struct ListNode*)malloc(sizeof(struct ListNode));
    if (tmp == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    struct ListNode* guard, * tail;
    guard = tail = tmp;
    //对哨兵位头结点进行初始化
    guard->next = NULL;

    //遍历两个链表
    while (list1 && list2)//若list1与list2其中一个为空指针就遍历结束。
    {
        //取小的尾插到新链表中
        if (list1->val < list2->val)
        {
            //链接
            tail->next = list1;
            //换尾
            tail = tail->next;
            list1 = list1->next;//移动到链表1的下一个结点
        }
        else
        {
            //链接
            tail->next = list2;
            //换尾
            tail = tail->next;
            list2 = list2->next; //移动到链表2的下一个结点
        }
    }

    //在尾插过程中,若某一链表已被遍历完毕,则直接将另一个未遍历完的链表剩下的结点尾插到新链表后面即可。
    if (!list1)
        tail->next = list2;
    if (!list2)
        tail->next = list1;

    //找新链表第一个有效结点
    struct ListNode* first = guard->next;
    //删除哨兵位头结点
    free(guard);
    return first;
}

3.测试代码

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

//链表结点的结构体类型
struct ListNode
{
    Listdata val;
    struct ListNode* next;
};

//创建结点
struct ListNode* BuySLTNode(Listdata x)
{
    struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
    if (node == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }

    //对新创建结点的成员进行初始化
    node->val = x;
    node->next = NULL;

    return node;
}

//创建n个结点的链表
struct ListNode* CreateSList(Listdata* arr, int n)
{
    struct ListNode* phead, * tail;//phead指向新创建链表的头结点、tail指向新创建链表的尾结点
    phead = tail = NULL;
    int i = 0;
    for (i = 0; i < n; i++)
    {
        //创建新结点
        struct ListNode* newnode = BuySLTNode(arr[i]);
        //判断phead是否是空链表
        if (phead == NULL)
        {
            phead = tail = newnode;
        }
        else//尾插
        {
            //把尾结点与新创建的结点链接起来
            tail->next = newnode;
            //换尾
            tail = tail->next;//让tail移动到新的尾结点,即此时新创建的结点就是新的尾结点。
        }
    }
    return phead;
}

//链表打印函数
void ListPrint(struct ListNode* phead)
{
    //assert(phead);//由于我们要打印合并后的链表,即使是空链表也要打印,所以这个打印函数就不判断phead是否是空链表了。
    struct ListNode* cur = phead;
    while (cur)
    {
        printf("%d->", cur->val);
        cur = cur->next;
    }
    printf("NULL\n");
}

//代码1:不带哨兵位头结点的写法
 struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
 {
     //判断list1与list2是否是空链表的情况
     if(list1 == NULL)
     return list2;//即使list2 = NULL,这种if语句的写法也可以表示list1与list2都是空链表的情况。
     if(list2 == NULL)
     return list1;

     struct ListNode*head , *tail;//指针head指向新链表的头结点、指针tail指向新链表的尾结点。
     head = tail = NULL;

     //遍历两个链表
     while(list1 && list2)//若list1与list2其中一个为空指针就遍历结束。
     {
         //取小的尾插到新链表中
         if(list1->val < list2->val)
         {
             //判断指针head指向的新链表是否是空链表。
             if(head == NULL)
             {
                 head = tail = list1;
             }
             else
             {
                 //链接
                 tail->next = list1;
                 //换尾
                 tail = tail->next;
             }

             list1 = list1->next;//移动到链表1的下一个结点
         }
         else
         {
             //判断指针head指向的新链表是否是空链表。
             if(head == NULL)
             {
                 head = tail = list2;  
             }
             else
             {
                 //链接
                 tail->next = list2;
                 //换尾
                 tail = tail->next;
             }

             list2 = list2->next; //移动到链表2的下一个结点
         }
     }

     //在尾插过程中,若某一链表已被遍历完毕,则直接将另一个未遍历完的链表剩下的结点尾插到新链表后面即可。
     if(!list1)
     tail->next = list2;
     if(!list2)
     tail->next = list1;

     return head;
 }

//代码2:带哨兵位头结点的写法
//struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
//{
//    // //判断list1与list2是否是空链表的情况
//    // if(list1 == NULL)
//    // return list2;//即使list2 = NULL,这种if语句的写法也可以表示list1与list2都是空链表的情况。
//    // if(list2 == NULL)
//    // return list1;
//    struct ListNode* tmp = (struct ListNode*)malloc(sizeof(struct ListNode));
//    if (tmp == NULL)
//    {
//        perror("malloc fail");
//        exit(-1);
//    }
//    struct ListNode* guard, * tail;
//    guard = tail = tmp;
//    //对哨兵位头结点进行初始化
//    guard->next = NULL;
//
//    //遍历两个链表
//    while (list1 && list2)//若list1与list2其中一个为空指针就遍历结束。
//    {
//        //取小的尾插到新链表中
//        if (list1->val < list2->val)
//        {
//            //链接
//            tail->next = list1;
//            //换尾
//            tail = tail->next;
//            list1 = list1->next;//移动到链表1的下一个结点
//        }
//        else
//        {
//            //链接
//            tail->next = list2;
//            //换尾
//            tail = tail->next;
//            list2 = list2->next; //移动到链表2的下一个结点
//        }
//    }
//
//    if (!list1)
//        tail->next = list2;
//    if (!list2)
//        tail->next = list1;
//
//    //找新链表第一个有效结点
//    struct ListNode* first = guard->next;
//    //删除哨兵位头结点
//    free(guard);
//    return first;
//}



void test()
{
    //测试用例:
    //例1:
    //Listdata arr1[] = { 1,2,4 };
    //Listdata arr2[] = { 1,3,4 };
    //struct ListNode* plist1 = CreateSList(arr1, sizeof(arr1) / sizeof(Listdata));//创建链表1
    //struct ListNode* plist2 = CreateSList(arr2, sizeof(arr2) / sizeof(Listdata));//创建链表2
    

    //用例2:
    //链表1与链表2都是空链表
   /* struct ListNode* plist1 = NULL;
    struct ListNode* plist2 = NULL;*/

    //用例3:
    struct ListNode* plist1 = NULL;//链表1是个空链表
    Listdata arr2[] = { 0 };
    struct ListNode* plist2 = CreateSList(arr2, sizeof(arr2) / sizeof(Listdata));//创建链表2


    //合并两个有序链表
    struct ListNode* mergelist = mergeTwoLists(plist1, plist2);
    //打印合并后的新链表
    ListPrint(mergelist);
}

int main()
{
    test();
    return 0;
}

四、链表分割

链表分割_牛客题霸_牛客网

题目描述:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

1.思路1步骤

(1)步骤1

(注意:假设x = 5)

搞两个带哨兵位头结点的新链表:把原链表中数据val小于x = 5的数据所在的结点取下来后尾插到第一个新链表中;把原链表中数据val大于等于x = 5的数据所在的结点取下来后尾插到第二个新链表中。(注意:第一和第二个新链表都要用带哨兵位头结点的单链表,如果不用的话会非常麻烦;两个新链表必须用带哨兵位头结点的单链表的原因:当所有数据data都比x大或者所有的数据data都比x小的话,此时会存在某个指针是空指针NULL,这样会存在对空指针进行解引用的风险。)

(2)步骤2

再把两个新链表链接起来合并成第三个新链表,再利用free函数删除第一和第二个新链表中的哨兵位头结点。

2.该题必须要处理的三种场景(以图形进行分析)

(1)场景1:原链表中的数据有比x大的,也有比x小的。

场景1又分为以下情况:

①情况1:原链表的最后一个结点的数据比x大

②情况2:原链表的最后一个结点的数据比x小(注意:若遇到这种情况一定要把第三个新链表尾结点的成员变量指针next设置为空指针NULL,否则的话可能第三个新链表会变成一个环形链表)

以下图形中第一和第二个新链表链接后变成第三个新链表,而val = 7的结点是第三个新链表的尾结点,由于val = 7的结点的成员变量指针next没有设置为空指针NULL进而导致了val = 7的结点的成员变量指针next指向val = 4的结点进而导致第三个新链表变成环形链表。(注意:为了防止val = 7的结点指向val = 4的结点,这里必须把val = 7的结点的成员变量指针next设置为空指针NULL)

(2)场景2:原链表中的所有数据都比x大。

(3)场景3:原链表中的所有数据都比x小。

3.代码

//链表存放的数据类型
typedef int Listdata;

//链表结点的结构体类型
struct ListNode
{
    Listdata val;
    struct ListNode* next;
};

//写法1:带哨兵位头结点
struct ListNode* partition(struct ListNode* pHead, int x)
{
    struct ListNode* lessHead, * lessTail, * greaterHead, * greaterTail;
    //创建两条链表各自的哨兵位头结点
    lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
    greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
    //对两条链表各自的哨兵位头结点进行初始化
    greaterHead->next = lessHead->next = NULL;

    struct ListNode* cur = pHead;
    //遍历原链表
    while (cur)
    {
        //遍历原链表取小的尾插到链表1
        if (cur->val < x)
        {
            //尾插
            lessTail->next = cur;
            lessTail = lessTail->next;
        }
        else
        {
            //尾插
            greaterTail->next = cur;
            greaterTail = greaterTail->next;
        }
        cur = cur->next;
    }

    //合并链表
    lessTail->next = greaterHead->next;//由于两个链表都带有哨兵位头结点,所以无需考虑两条链表是空链表这一情况。
    //把合并后的新链表链表的尾结点的成员变量next设置为空指针NULL。
    greaterTail->next = NULL;//这里把尾结点的成员变量next设置为空指针NULL的目的是为了防止当原链表中的数据有比x大的也有比x小的时原链表的最后一个结点的数据比x小的这种情况会使得新链表变成环形链表。

    //让指针pHead指向合并后新链表的第一个有效结点
    pHead = lessHead->next;

    //销毁两条链表的哨兵位头结点
    free(lessHead);
    free(greaterHead);

    //返回合并链表的头结点指针
    return pHead;
}

4.测试代码

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


//链表存放的数据类型
typedef int Listdata;

//链表结点的结构体类型
struct ListNode
{
    Listdata val;
    struct ListNode* next;
};

//创建结点
struct ListNode* BuySLTNode(Listdata x)
{
    struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
    if (node == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }

    //对新创建结点的成员进行初始化
    node->val = x;
    node->next = NULL;

    return node;
}

//创建n个结点的链表
struct ListNode* CreateSList(Listdata* arr, int n)
{
    struct ListNode* phead, * tail;//phead指向新创建链表的头结点、tail指向新创建链表的尾结点
    phead = tail = NULL;
    int i = 0;
    for (i = 0; i < n; i++)
    {
        //创建新结点
        struct ListNode* newnode = BuySLTNode(arr[i]);
        //判断phead是否是空链表
        if (phead == NULL)
        {
            phead = tail = newnode;
        }
        else//尾插
        {
            //把尾结点与新创建的结点链接起来
            tail->next = newnode;
            //换尾
            tail = tail->next;//让tail移动到新的尾结点,即此时新创建的结点就是新的尾结点。
        }
    }
    return phead;
}

//链表打印函数
void ListPrint(struct ListNode* phead)
{
    assert(phead);
    struct ListNode* cur = phead;
    while (cur)
    {
        printf("%d->", cur->val);
        cur = cur->next;
    }
    printf("NULL\n");
}

//题目:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

//写法1:带哨兵位头结点
struct ListNode* partition(struct ListNode* pHead, int x)
{
    struct ListNode* lessHead, * lessTail, * greaterHead, * greaterTail;
    //创建两条链表各自的哨兵位头结点
    lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
    greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
    //对两条链表各自的哨兵位头结点进行初始化
    greaterHead->next = lessHead->next = NULL;

    struct ListNode* cur = pHead;
    //遍历原链表
    while (cur)
    {
        //遍历原链表取小的尾插到链表1
        if (cur->val < x)
        {
            //尾插
            lessTail->next = cur;
            lessTail = lessTail->next;
        }
        else
        {
            //尾插
            greaterTail->next = cur;
            greaterTail = greaterTail->next;
        }
        cur = cur->next;
    }

    //合并链表
    lessTail->next = greaterHead->next;//由于两个链表都带有哨兵位头结点,所以无需考虑两条链表是空链表这一情况。
    //把合并后的新链表链表的尾结点的成员变量next设置为空指针NULL。
    greaterTail->next = NULL;

    //让指针pHead指向合并后新链表的第一个有效结点
    pHead = lessHead->next;

    //销毁两条链表的哨兵位头结点
    free(lessHead);
    free(greaterHead);

    //返回合并链表的头结点指针
    return pHead;
}


void test()
{
    //测试用例:
    Listdata arr[] = { 2,3,6,7,4,2 };//x = 5
    //Listdata arr[] = { 5,7,8,9 };//x = 4
    //Listdata arr[] = { 1,4,2,5 };//x = 6

    //创建n个结点的链表
    struct ListNode* plist = CreateSList(arr, sizeof(arr) / sizeof(Listdata));

    //创建空链表
    //struct ListNode* plist = NULL;//这个也是测试用例

    //输入x的值
    int x = 0;
    scanf("%d", &x);

    //链表分割
    plist = partition(plist, x);

    //打印链表分割后的情况
    ListPrint(plist);
}


int main()
{
    test();
    return 0;
}

五、链表的回文结构

链表的回文结构_牛客题霸_牛客网

1.单链表回文结构的定义

链表回文结构指的是一种特殊的链表,其中数据元素的排列顺序在正向和反向读取时是相同的。这意味着链表从前向后读和从后向前读得到的结果是一样的。例如,链表1->2->3->2->1就是一个回文链表。

2.解题思路

2.1思路1

(注意:由于思路1的空间复杂的是O(1),所以在这里我着重说明思路1如何实现)

先找到原链表的中间结点,然后用链表的中间结点区分链表的前半段和后半段。把链表中间结点的前面部分称为链表的前半段,而把链表中间结点的后面部分称为链表的后半段(注意:其中中间结点也算链表的后半段的内容)。然后反转原链表的后半段后得到了个新链表,然后再拿新链表与原链表的前半段进行比较,然后判断新链表与原链表的前半段中的所有数据是否都相等,若都相同的话说明原链表是个回文结构的单链表,若不同的话则说明原链表不是个回文结构的单链表。

2.2.思路2:

(注意:由于思路2的空间复杂度是O(N),则在这里我就不实现了)

先利用第一个新链表拷贝原链表中的所有数据,然后再把第一个新链表逆序后得到第二个新链表 ,并把第二个新链表与原链表按顺序进行比较,若第二个新链表与原链表中的所有数据data都相同的话,则说明原链表是个回文结构的单链表;若第二个新链表与原链表有一个数据data不相同的话,则说明原链表不是回文结构的单链表。

(思路2的解析:要想判断该链表是否是回文结构,只需判断链表中的所有数据是否是对称的,由于链表不像数组一样可以倒着访问数据,所以无法直接在原链表中判断链表左右的数据是否对称。把原链表逆序后得到的逆序链表,而且逆序链表和原链表的数据是颠倒过来的。由于逆序链表和原链表的数据是颠倒过来的,所以可以通过按顺序判断逆序链表和原链表中所有数据是否都相同就可以判断出原链表是否是个回文结构的单链表。)

注意:

(1)思路2的空间复杂度是O(n)

(2)思路2是必须要用一个新链表拷贝原链表中的所有数据之后再去逆序这个新链表而且原链表不能逆序

(3)回文结构的单链表逆序后还是个回文结构的单链表,而不是回文结构的单链表逆序后依然不是个回文结构的单链表。

2.3思路3

(注意:思路3与思路2的想法类似而且思路3的空间复杂度也是O(N),所以在这里我就不实现了)

由于单链表不能倒着走,而数组的元素访问可以倒着走,所以可以把链表中的所有数据存放到数组中,然后通过判断数组中的元素是不是对称的,若数组中的元素是对称的说明这个单链表是回文结构,若数组中的元素不是对称的说明这个单链表不是回文结构。

3.思路1的实现

3.1.思路1的思路

先找到原链表的中间结点,然后用链表的中间结点区分链表的前半段和后半段。把链表中间结点的前面部分称为链表的前半段,而把链表中间结点的后面部分称为链表的后半段(注意:其中中间结点也算链表的后半段的内容)。然后反转原链表的后半段后得到了个新链表,然后再拿新链表与原链表的前半段进行比较,然后判断新链表与原链表的前半段中的所有数据是否都相等,若都相同的话说明原链表是个回文结构的单链表,若不同的话则说明原链表不是个回文结构的单链表。(注意:由于思路1的空间复杂的是O(1))

3.2.该题必须解决的两种场景

(1)场景1:利用思路1判断偶数个结点的链表是否是回文结构

图形解析

(2)场景2:利用思路1判断奇数个结点的链表是否是回文结构

图形解析

3.3.代码实现

图形解析

代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//链表存放的数据类型
typedef int Listdata;

//链表结点的结构体类型
struct ListNode
{
    Listdata val;
    struct ListNode* next;
};


//找中间结点函数

//找中间结点函数代码1:快慢指针法->如果有两个中间结点,则返回第2个中间结点。
//注意:一定要考虑链表结点的个数是奇数还是偶数
struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* fast, * slow;
    fast = slow = head;
    //注意:当链表的结点数是偶数个的时候,题目要求的是返回第二个中间结点。
    while (fast && fast->next)//while循环的判断表达式中fast针对的是偶数个结点、fast->next针对的时奇数个结点。
    {
        slow = slow->next;
        fast = fast->next->next;
    }

    return slow;//当while循环结束时,若链表有奇数个结点则慢指针指针slow恰好指向链表的中间结点;若链表有偶数个结点则慢指针指针slow恰好指向链表的第二个中间结点。
}



//反转链表函数

//反转链表函数的代码1:头插法->从头遍历原链表并把原链表的所有结点一个一个的取下来头插到新链表中
 //struct ListNode* reverseList(struct ListNode* head)
 //{
 //    //判断指针head指向的链表是否为空链表
 //    if (head == NULL)
 //        return NULL;
 //    struct ListNode* cur = head;//指针cur用来遍历原链表
 //    struct ListNode* rhead = NULL;//指针rhead指向新链表的头结点

 //    //遍历原链表
 //    while (cur)
 //    {
 //        //找当前cur位置的下一个结点
 //        struct ListNode* next = cur->next;
 //        if (rhead == NULL)
 //        {
 //            rhead = cur;
 //            rhead->next = NULL;//此时rhead指向新链表的尾结点和头结点,并把新链表尾结点的成员next置为空指针NULL。
 //            cur = next;//让cur移动到原链表的下一个结点
 //        }
 //        else
 //        {
 //            //从原链表中取下一个结点头插到rhead指向的新链表中。
 //            cur->next = rhead;
 //            //头插完成后,要更新rhead的指向使得rhead指向新的头结点。
 //            rhead = cur;//换头
 //            //让cur移动到原链表的下一个结点
 //            cur = next;
 //        }
 //    }

 //    return rhead;
 //}


 //反转链表函数的代码2:三指针n1、n2、n3
struct ListNode* reverseList(struct ListNode* head)
{
    //判断指针head指向的链表是否为空链表
    if (head == NULL)
        return NULL;

    struct ListNode* n1, * n2, * n3;
    n1 = NULL;//n1指向原链表n2位置的上一个结点
    n2 = head;//n2指向原链表的当前结点。用指针n2遍历整个链表。
    n3 = n2->next;//n3指向原链表n2位置的下一个结点
    while (n2)
    {
        //反转
        n2->next = n1;//利用n2指向n1来达到反转原链表的一个结点

        //移动
        n1 = n2;//n1移动到n2的位置
        n2 = n3;//n2移动到n3的位置
        if (n3)//当n2指向原链表的尾结点时,此时n3的值为空指针NULL,为了防止对空指针n3进行解引用则必须加一个if语句判断n3是否是空指针。
            n3 = n3->next;//n3移动到下一个结点的位置
    }

    return n1;//当while(n2)循环结束时,此时n2的值为空指针NULL,而n1指向反转链表的头结点。
}



//判断链表是否是回文结构
bool chkPalindrome(struct ListNode* head)
{
    struct ListNode* mid = middleNode(head);//找链表中间结点。若链表是偶数个结点则找链表的第二个中间结点。找到中间结点后就可以用链表的中间结点区分链表的前半段和后半段。指针mid指向链表的后半段。

    struct ListNode* rhead = reverseList(mid);//从原链表的中间结点开始反转原链表的后半段后得到了个新链表。指针rhead指向新链表。然后再拿新链表与原链表的前半段进行比较,然后判断新链表与原链表的前半段中的所有数据是否都相等,若都相同的话说明原链表是个回文结构的单链表,若不同的话则说明原链表不是个回文结构的单链表。

    //比较新链表与原链表前半段的过程
    while (head && rhead)//在while循环中只要head和rhead其中一个为空指针都能说明指针head指向的链表是回文结构。
    {
        //遍历指针head与指针rhead指向的两条链表,并判断当前head位置和rhead位置结点的值是否相等直到两条链表至少有1条链表能够遇到控制者NULL才能说明这个链表是回文结构。若在遍历两条链表的过程中发现有一个结点的值不相等,则说明这个链表不是回文结构。
        if (head->val != rhead->val)
            return false;
        head = head->next;
        rhead = rhead->next;
    }

    return true;
}

六、相交链表

. - 力扣(LeetCode)

1.对相交链表的理解

1.1.相交链表的定义

相交链表是指两个链表在某个节点开始共享相同的节点,这意味着从该节点开始,两个链表的其余部分是相同的。换句话说,两个链表至少有一个共同的节点,这个节点可以是它们各自链表的中间节点,也可以是尾节点。

1.2.相交链表的图形结构

相交单链表的实际图形

(注意:由于链表结点的结构体类型的所有成员变量中只有一个指针next进而导致两条链表的第一个交点的成员变量指针next只能存放一个结点的地址导致相交链表的图形不像数学中两条相交直线只有一个交点的图形。相交链表的交点至少有一个)

小结

①相交链表的图形可以简记为Y字型:

②数学中两条相交直线的图形:

2.判断两条链表是否有相同结点的思路

(1)判断两条链表是否有相同的结点的思路

若两条链表的尾结点相同则证明两条链表存在相同结点进而证明两条链表是相交链表。(注意:若两条链表的尾结点的地址相同则说明这两条链表相交)

(2)判断两个结点是否是相同结点的思路

由于每个结点中的成员变量数据val的值是可能都不相等使得不能用结点的成员变量数据val来判断两个结点是否相等。同时由于每个结点的地址是唯一的使得可以用结点的地址来判断两个结点是否相等,即可以利用结点的地址判断两条链表是否存在两个相同的结点进而判断两条链条是否存在相交的结点。

总的来说,要判断两条链条是否存在相交的结点就必须用结点的地址来判断两个结点是否相等。

3.判断两条链表是否是相交链表,若是则返回第一个交点的地址的思路

3.1.解题思路

(1)思路1——先判断是否是相交链表,再找出第一个交点

思路描述:总的来说,先判断两条链表是否是相交链表在判断的同时计算两条链表各自的长度lenA和lenB,若两条链表是相交链表的话,则计算两条链表长度的差值gap = abs(lenA - lenB),然后利用两条链表长度的差值gap让长链表先走gap(注意:让长的链表先走gap步的目的是让长链表剩余的长度和短链表的长度一样),在长链表走完gap步后此时长链表剩余的长度就和短链表的长度一样,由于长链表和短链表的长度一样则此时同时遍历长链表和短链表并比较两个链表相同位置的结点是否相同,若发现了第一个相同的结点则此时这个结点就是两条链表的第一个相交的结点。

结论:在两条链表是相交链表的前提下,如果两条链表是一样长的话,则要想找出两条链表的第一个相交结点的方式不再是用链表B中的每个结点与链表A中的每个结点一 一进行比较一遍,而是只需比较链表B和链表A的对应位置的结点是否有相同的结点就可以找出相交链表的第一个相交的结点。

(注意:思路1的时间复杂度是O(n);两条链表若是相交链表的话,由于两条链表的第一个相交结点(结构体)的所有成员变量中只有一个指针next存放一个结点的地址导致两条链表是不能逆序的)

(2)思路2 —— 暴力查找:判断两条链表是否相交的同时再找出第一个交点

(注意:思路2的时间复杂度是O(n^2);链表B有n个结点)

思路描述:在不知道两条链表是否相交而且也不知道两条链表的长度是否相同的情况下,要判断两条链表是否相交的同时找出相交的第一个交点的思路是判断链表B的某个结点是否是链表A的结点,判断成功则链表B这个结点就是第一个相交结点。而判断链表B的某个结点是否是链表A的结点的思路是把链表A的每个结点都要与链表B的某个结点进行比较,若存在相等的结点则说明两条链表相交而且这个相等的结点就是第一个交点;若始终在链表A中找不到一个结点与链表B的某个结点相等则说明这两条链表不是相交链表。在最坏的情况下,只有链表B的尾结点是链表A的结点或者连链表B的尾结点都不是链表A的结点,则此时最坏情况的时间复杂度是O(n^2)。

(3)思路3:利用带头双向循环链表判断是否是相交链表并找出第一个交点

 思路描述:若两条链表都是是个带头循环双向链表的话,可以从链表的后面倒者走来快速判断两条链表是否是相交链表同时也可以快速找出两条链表的第一个相交的结点。若两条链表的尾结点都不相等则说明这两条链表不是相交链表。

3.2.思路1的代码实现

(注意:由于题目给出的链表是不带头不循环的单链表,而且思路2的时间复杂度高,所以这题我们着重利用思路1解题)

#include <stdio.h>

//题目描述:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

//链表存放的数据类型
typedef int Listdata;

//链表结点的结构体类型
struct ListNode
{
    Listdata val;
    struct ListNode* next;
};

//找相交链表的第一个交点
struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{
    struct ListNode* curA = headA;
    struct ListNode* curB = headB;

    int lenA = 0;
    int lenB = 0;

    //计算两条链表的长度
    while (curA->next)
    {
        lenA++;
        curA = curA->next;
    }

    while (curB->next)
    {
        lenB++;
        curB = curB->next;
    }
    //若两条链表的尾结点的地址都不相等则说明这两条不是相交链表
    if (curA != curB)
        return NULL;

    int gap = abs(lenA - lenB);
    struct ListNode* shortList = headA;
    struct ListNode* longList = headB;
    if (lenA > lenB)
    {
        shortList = headB;
        longList = headA;
    }

    //长链表先走gap步
    while (gap--)
    {
        longList = longList->next;
    }

    //同时遍历两条链表,找出第一个相同的结点就是第一个相交结点。
    //找第一个相交结点写法1:
   /* while (1)
    {
        if (longList == shortList)
            return longList;
        else
        {
            longList = longList->next;
            shortList = shortList->next;
        }
    }*/

    //找第一个相交结点写法2
     while(longList != shortList)
     {
         longList = longList->next;
         shortList = shortList->next;
     }
     return longList;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值