前言

鉴于顺序表在内存中存储有诸多缺点,其中典型的就是数组,数组每个元素在内存中都是连续存放的,导致每次在头部或者中间插入新元素都需要挪动已有的元素,而链表就恰好弥补了顺序表的一些缺点。本篇文章主要讲述两种链表的实现以及链表的应用实例。

单链表

关于单链表的定义很简单,形如下图所示:

有一定数量的节点,每个节点除了存储数据,还存储了下一个节点的地址,那么有了首节点的地址,就可以访问链表的所有内容了。
根据此思路,就可以实现单链表了,代码如下:

//SList.h头文件,关于类型定义,函数声明以及库函数头文件的包含
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDateType;//重定义存储数据的类型,提高代码的通用性
typedef struct SListNode//定义单链表的节点
{SLTDateType data;//数据内容struct SListNode* next;//节点地址
}SListNode;//类型重定义// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter( SListNode* pos, SLTDateType x);
//在之前插入
void SListInsert(SListNode** pplist, SListNode* pos, SLTDateType x);
// 单链表删除pos位置的值
void SListErase(SListNode** pplist, SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode** pplist);
//单链表相关函数的实现  SList.c
#include "SList.h"
//向内存申请节点
SListNode* BuySListNode(SLTDateType x)
{SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));if (newnode == NULL){perror("malloc fail\n");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}
//单链表头部插入数据
void SListPushFront(SListNode** pplist, SLTDateType x)//采用二级指针,原因在于需要修改头节点的地址
{assert(pplist);//头节点地址的地址不可能为空,即使是在链表为空的情况下,存储链表首节点地址(NULL)的变量依旧存在,所以指向这个变量的地址不可能为空SListNode* newnode = BuySListNode(x);assert(newnode);newnode->next = *pplist;*pplist = newnode;
}
//链表的打印
void SListPrint(SListNode* plist)//这里采用一级指针,原因在于不会修改头节点的地址
{while (plist != NULL){printf("%d->", plist->data);plist = plist->next;}printf("NULL\n");
}
//单链表的尾部插入
void SListPushBack(SListNode** pplist, SLTDateType x)//这里采用二级指针,原因在于考虑到可能存在链表为空的情况,尾部插入也需要修改链表头结点的地址
{assert(pplist);SListNode* newnode = BuySListNode(x);assert(newnode);SListNode* cur = *pplist;if (cur == NULL){*pplist = newnode;}else{while (cur->next != NULL){cur = cur->next;}cur->next = newnode;}
}
//单链表的头部删除
void SListPopFront(SListNode** pplist)//会修改头结点的地址
{assert(pplist);if (*pplist == NULL)//空链表不用删除{return;}SListNode* cur = *pplist;*pplist = (*pplist)->next;free(cur);cur = NULL;
}
//单链表的尾部删除
void SListPopBack(SListNode** pplist)//可能会改变头节点地址
{assert(pplist);if (*pplist == NULL)//空链表不用删除{return;}SListNode* cur = *pplist;if (cur->next == NULL)//链表只有一个节点时,删除会改变头节点的地址{free(*pplist);*pplist = NULL;return;}while (cur->next->next != NULL)//判断某个节点的下一个节点是不是尾节点{cur = cur->next;}free(cur->next);cur->next = NULL;
}
//查找节点
SListNode* SListFind(SListNode* plist, SLTDateType x)
{SListNode* cur = plist;while (cur != NULL && cur->data != x){cur = cur->next;}return cur;
}
//在pos位置前插入节点
void SListInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{assert(pplist);assert(pos);if (*pplist == pos)//pos是头结点时,相当于头部插入{SListPushFront(pplist, x);return;}else{SListNode* newnode = BuySListNode(x);assert(newnode);SListNode* cur = *pplist;while (cur->next != pos){cur = cur->next;assert(cur);}newnode->next = cur->next;cur->next = newnode;}
}
//在pos位置之后插入
void SListInsertAfter(SListNode* pos, SLTDateType x)
{assert(pos);SListNode* newnode = BuySListNode(x);assert(newnode);newnode->next = pos->next;pos->next = newnode;
}
//链表的销毁
void SListDestroy(SListNode** pplist)
{assert(pplist);SListNode* cur = *pplist;while (cur){SListNode* del = cur;cur = cur->next;free(del);del = NULL;}*pplist = NULL;
}
//链表节点的删除
void SListErase(SListNode** pplist, SListNode* pos)
{assert(pplist);assert(pos);SListNode* cur = *pplist;if (cur == pos)//当删除的节点是头节点时{*pplist = pos->next;free(cur);return;}while (cur->next != pos)//不是头结点时{cur = cur->next;assert(cur);}cur->next = pos->next;free(pos);
}
//测试
#include "SList.h"
//测试头部和尾部插入
void test1()
{SListNode* Head = NULL;SListPushFront(&Head, 1);SListPushFront(&Head, 2);SListPushFront(&Head, 3);SListPushFront(&Head, 4);SListPrint(Head);SListPushBack(&Head, 1);SListPushBack(&Head, 2);SListPushBack(&Head, 3);SListPushBack(&Head, 4);SListPrint(Head);
}
测试头部和尾部的删除
void test2()
{SListNode* Head = NULL;SListPushFront(&Head, 1);SListPushFront(&Head, 2);SListPushFront(&Head, 3);SListPushFront(&Head, 4);SListPrint(Head);SListPushBack(&Head, 1);SListPushBack(&Head, 2);SListPushBack(&Head, 3);SListPushBack(&Head, 4);SListPrint(Head);SListPopFront(&Head);SListPopFront(&Head);SListPopFront(&Head);SListPopFront(&Head);SListPrint(Head);SListPopBack(&Head);SListPopBack(&Head);SListPopBack(&Head);SListPopBack(&Head);SListPrint(Head);
}
//测试查找,指定位置的插入和删除以及链表的销毁
void test3()
{SListNode* Head = NULL;SListPushFront(&Head, 1);SListPushFront(&Head, 2);SListPushFront(&Head, 3);SListPushFront(&Head, 4);SListPrint(Head);SListNode* cur = SListFind(Head, 8);if (cur != NULL)printf("找到了\n");elseprintf("找不到\n");SListNode* pos = SListFind(Head, 1);SListInsert(&Head, pos, 20);SListPrint(Head);SListInsertAfter(pos, 20);SListPrint(Head);SListErase(&Head, pos);SListPrint(Head);SListDestroy(&Head);SListPrint(Head);
}
int main()
{test3();return 0;
}

通过实现单链表的功能函数,可以发现,头部插入删除和尾部插入删除可以通过调用SListInsert,SListInsertAfter,SListErase三个函数实现,改造后的代码如下:

void SListPushFront(SListNode** pplist, SLTDateType x)
{/*assert(pplist);SListNode* newnode = BuySListNode(x);assert(newnode);newnode->next = *pplist;*pplist = newnode;*/SListInsert(pplist, *pplist, x);
}void SListPushBack(SListNode** pplist, SLTDateType x)
{assert(pplist);SListNode* newnode = BuySListNode(x);assert(newnode);SListNode* cur = *pplist;if (cur == NULL){*pplist = newnode;}else{while (cur->next != NULL){cur = cur->next;}SListInsertAfter(cur, x);}
}void SListPopFront(SListNode** pplist)
{/*assert(pplist);if (*pplist == NULL){return;}SListNode* cur = *pplist;*pplist = (*pplist)->next;free(cur);cur = NULL;*/SListErase(pplist, *pplist);
}void SListPopBack(SListNode** pplist)
{assert(pplist);if (*pplist == NULL){return;}SListNode* cur = *pplist;while (cur->next != NULL){cur = cur->next;}SListErase(pplist, cur->next);
}

带头双向循环链表

通过对代码的简化,我们可以发现,尾部插入和删除都需要去找尾或者在某个节点之前插入新节点也需要从头寻找这个节点的前节点,所以相对顺序表,单向链表并没有把顺序表的缺点完美解决,因此设计一种找尾时间复杂度为O(1)的算法就显得更好,这就引出了带头双向循环链表。
带头双向循环链表形如下图:

这种双向链表的优点在于:找链表尾部的时间复杂度位O(1);找某个节点的前一个结点也很方便。接下来实现带头双向循环链表:

//List.h,库函数头文件的包含以及函数的声明,类型的定义
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
//节点存储的数据类型
typedef int LTDataType;
//节点定义
typedef struct ListNode
{LTDataType data;struct ListNode* next;struct ListNode* prev;
}ListNode;// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
//list.c,链表功能函数的实现
#include "list.h"
//创建头节点
ListNode* ListCreate()
{ListNode* guard = (ListNode*)malloc(sizeof(ListNode));if (guard == NULL){perror("malloc fail");exit(-1);}guard->next = guard;guard->prev = guard;//guard的next和prev指向自身,构成循环return guard;
}
//创建节点
ListNode* BuyListNode(LTDataType x)
{ListNode* node = (ListNode*)malloc(sizeof(ListNode));assert(node);node->data = x;node->next = NULL;node->prev = NULL;return node;
}
//在指定位置前插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* prev = pos->prev;ListNode* newnode = BuyListNode(x);prev->next = newnode;newnode->next = pos;pos->prev = newnode;newnode->prev = prev;
}
//尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{//方法1:/*assert(pHead);//不论链表是否为空,哨兵位的头节点一定存在,即pHead不为空ListNode* newnode = BuyListNode(x);newnode->next = pHead;pHead->prev->next = newnode;newnode->prev = pHead->prev;pHead->prev = newnode;*///方法2:ListInsert(pHead, x);
}
//打印
void ListPrint(ListNode* pHead)
{assert(pHead);//pHead一定不为空ListNode* cur = pHead->next;while (cur != pHead)//哨兵位的头节点guard不用打印{printf("%d<==>", cur->data);cur = cur->next;}printf("\n");
}
//头插
void ListPushFront(ListNode* pHead, LTDataType x)
{/*assert(pHead);ListNode* newnode = BuyListNode(x);newnode->next = pHead->next;pHead->next->prev = newnode;pHead->next = newnode;newnode->prev = pHead;*/ListInsert(pHead->next, x);}
//判断链表是否为空
bool ListEmpty(ListNode* pHead)
{assert(pHead);return pHead->next == pHead;
}
//尾删
void ListPopBack(ListNode* pHead)
{assert(pHead);assert(!ListEmpty(pHead));//判断链表是否为空,为空不能删除//方法1:/*ListNode* del = pHead->prev;pHead->prev = del->prev;del->prev->next = pHead;free(del);del = NULL;*///方法2:ListErase(pHead->prev);}
//头删
void ListPopFront(ListNode* pHead)
{assert(pHead);assert(!ListEmpty(pHead));//判断链表是否为空,为空不能删除//方法1:/*ListNode* del = pHead->next;pHead->next = del->next;del->next->prev = pHead;free(del);del = NULL;*///方法2:ListErase(pHead->next);
}
//查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead)//哨兵位的头节点不存储数据{if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
//指定位置删除
void ListErase(ListNode* pos)
{assert(pos);ListNode* prev = pos->prev;prev->next = pos->next;pos->next->prev = prev;free(pos);pos = NULL;
}
//销毁
void ListDestory(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){ListNode* next = cur->next;free(cur);cur = next;}free(pHead);
}
//test.c
#include "list.h"
//插入
void Test1()
{ListNode* plist = ListCreate();//尾插ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPrint(plist);//头插ListPushFront(plist, 1);ListPushFront(plist, 2);ListPushFront(plist, 3);ListPushFront(plist, 4);//打印ListPrint(plist);ListDestory(plist);plist = NULL;}
//删除
void Test2()
{ListNode* plist = ListCreate();//尾插ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);//头插ListPushFront(plist, 1);ListPushFront(plist, 2);ListPushFront(plist, 3);ListPushFront(plist, 4);//打印ListPrint(plist);//尾删ListPopBack(plist);ListPopBack(plist);ListPrint(plist);ListPopFront(plist);ListPopFront(plist);ListPrint(plist);ListDestory(plist);plist = NULL;
}
//查找和定位插入
void Test3()
{ListNode* plist = ListCreate();//尾插ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);//头插ListPushFront(plist, 1);ListPushFront(plist, 2);ListPushFront(plist, 3);ListPushFront(plist, 4);//打印ListPrint(plist);//查找ListNode* ret = ListFind(plist, 2);if (ret){printf("找到了\n");}//定位插入ListInsert(plist, 4);ListPrint(plist);ListInsert(plist->next, 4);ListPrint(plist);ListDestory(plist);plist = NULL;
}
//定位删除
void Test4()
{ListNode* plist = ListCreate();//尾插ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPrint(plist);//头插ListPushFront(plist, 1);ListPushFront(plist, 2);ListPushFront(plist, 3);ListPushFront(plist, 4);//打印ListPrint(plist);//定位删除ListErase(plist->next->next);ListPrint(plist);ListDestory(plist);plist = NULL;}
int main()
{Test4();return 0;
}

带头双向循环链表就完美的解决了单向链表尾插,尾删需要遍历整个链表的问题,同时查找某个节点的前一个结点的时间复杂度变为了O(1)。带头双向循环链表提到了带头,这个哨兵位的头节点其实提供了很多便利,例如:(1)不用再传二级指针;(2)在首尾两端的插入和删除就可以不需要再单独考虑链表是否为空的情况,当然在调用ListErase函数时不能传递哨兵位头节点的地址进行删除。

实例解决思路分享

1.找中间节点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点链接。
这里需要说明一下,一般题中出现的头节点不是指哨兵位的头节点,而是链表的第一个节点
这道题有个很巧妙的思路,利用快慢指针,即快指针一次走两步,慢指针一次走一步,当快指针到链表尾部时,慢指针恰好就在中点,如此设计时间复杂度为O(1),这种算法需要考虑链表节点数的奇偶,解决代码如下:

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
struct ListNode* middleNode(struct ListNode* head){struct ListNode* slow = head;struct ListNode* fast = head;while(fast && fast->next){fast = fast->next->next;slow = slow->next;}return slow;
}

2.找倒数第K个节点

输入一个链表,输出该链表中倒数第k个结点
这个问题也可以通过快慢指针来解决,即先让快指针走k步,然后快慢指针同步走,当快指针指向空时,慢指针就是倒数第k个节点,这里需要注意一下,当k大于节点数或者k小于时,就不存在倒数第k个节点,解决代码如下:

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {struct ListNode* fast = pListHead;struct ListNode* slow = pListHead;if(k<0 || !pListHead)return NULL;while(k--){fast = fast->next;if(!fast&&k)return NULL;}while(fast){fast = fast->next;slow = slow->next;}return slow;
}

3.合并有序链表

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
这道题的思路是:先创建一个哨兵位的头节点,然后分别通过指向两个链表的指针p1,p2比较数据的大小,哪个小就将哪个链接到以哨兵位头节点为首的新链表上,当p1,p2任意一个指向空时,再将不为空的指针连接到新链表上即可,解决代码如下:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));assert(guard);struct ListNode* tail = guard;struct ListNode* p1 = list1;struct ListNode* p2 = list2;if(!list1 && !list2)//两链表同时为空返回空{return NULL;}while(p1&&p2){if(p1->val<p2->val){tail->next = p1;tail = tail->next;p1 = p1->next;}else{tail->next = p2;tail = tail->next;p2 = p2->next;}}if(p1){tail->next = p1;}if(p2){tail->next = p2;}struct ListNode* newhead = guard->next;free(guard);guard = NULL;return newhead;
}

4.链表的分割

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针
这道题的思路为:创建两个哨兵位的头节点guard1,guard2,将小于x的节点尾插到guard1上,大于等于x的节点尾插到guard2上,最后再将两个链表串联起来,问题就解决了,当然新链表的尾部需要注意一下,解决代码如下:

/*struct ListNode {int val;struct ListNode *next;ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {public:ListNode* partition(ListNode* pHead, int x) {struct ListNode* guard1 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* guard2 = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* cur = pHead;struct ListNode* tail1 = guard1;struct ListNode* tail2 = guard2;while(cur){if(cur->val<x){tail1->next = cur;cur = cur->next;tail1 = tail1->next;}else{tail2->next = cur;cur = cur->next;tail2 = tail2->next;}}tail1->next = guard2->next;tail2->next = NULL;pHead = guard1->next;free(guard1);free(guard2);return pHead;}
};

5.回文结构

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900
解决这道题就需要用到前面提到的思路了,首先利用快慢指针找到链表中点,然后将链表中点以后的节点逆序(关于逆序实际上就是头插),最后就是通过两个指针分别指向链表头部和中点,进行数据对比,判断出结果,解决代码如下:

/*
struct ListNode {int val;struct ListNode *next;ListNode(int x) : val(x), next(NULL) {}
};*/
struct ListNode* middleNode(struct ListNode* head) {struct ListNode* slow = head;struct ListNode* fast = head;while (fast && fast->next) {fast = fast->next->next;slow = slow->next;}return slow;
}class PalindromeList {public:bool chkPalindrome(ListNode* A) {struct ListNode* mid = middleNode(A);//逆置struct ListNode* cur = mid->next;struct ListNode* next = NULL;mid->next = NULL;while(cur){next = cur->next; cur->next = mid;mid = cur;cur = next;}//通过两个指针,比较数据cur = A;while(mid){if(mid->val!=cur->val){return false;}mid = mid->next;cur = cur->next;}return true;}
};

6.公共节点

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
这道题的思路是:两链表相交,尾节点一定重合,那么先求出两个链表的长度,长的链表先走差距步,然后齐步走,直至两链表某节点地址一样,解决代码如下:

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {struct ListNode* curA = headA;struct ListNode* curB = headB;int lenA = 1;int lenB = 1;//求长度while(curA->next){lenA++;curA = curA->next;}while(curB->next){lenB++;curB = curB->next;}//两链表相交,尾节点一定重合,否则不相交if(curA!=curB){return NULL;}struct ListNode* shortLine = headA;struct ListNode *longLine = headB;if(lenA>lenB){shortLine = headB;longLine = headA;}//长度差距int count = abs(lenA-lenB);//长的链表先走差距步while(count--){longLine = longLine->next;}//同步走,当节点地址相同时,即为相交点while(shortLine!=longLine){longLine = longLine->next;shortLine = shortLine->next;}return longLine;
}

7.判断链表是否有环

给你一个链表的头节点 head ,判断链表中是否有环
这道题的思路是:利用快慢指针,如果链表有环满指针最终会追上快指针,否则快指针会指向空,解决代码如下:

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
bool hasCycle(struct ListNode *head) {struct ListNode * fast = head;struct ListNode * slow = head;//保证链表不为空if(!head){return false;}//快指针能被慢指针追上,说明有环,否则说明没环while(fast&&fast->next){//快指针每次走两步,慢指针每次走一步fast = fast->next->next;slow = slow->next;if(slow==fast){return true;}}return false;
}

8.判断入环的第一个节点

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL
这道题有两个解决思路:
(1)代数法,假设链表如下图所示:

假设带环链表如上所示,环上有C个节点,快指针每次走两步,慢指针每次走一步,当慢指针追上快指针时,快指针走了k×C+L+X个节点,k为走的圈数,慢指针走了L+X个节点(慢指针不可能再相遇前走完整圈,因为当慢指针入环时,快慢指针差距最大就是C-1,之后每走一步,差距减小1),所以就有2×(L+X)=k×C+L+X,转化可得:L=(k-1)×C+C-X,这个表达式的意思是:当一个指针从头开始走,一个指针从相遇点开始走,当这两个指针相遇时,即为入环的第一个节点,代码实现如下:

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
struct ListNode *detectCycle(struct ListNode *head) {struct ListNode * fast = head;struct ListNode * slow = head;//保证链表不为空if(!head){return false;}//快指针能被慢指针追上,说明有环,否则说明没环while(fast&&fast->next){//快指针每次走两步,慢指针每次走一步fast = fast->next->next;slow = slow->next;if(slow==fast){struct ListNode* pa = slow;struct ListNode* pb = head;while(pa!=pb){pa = pa->next;pb = pb->next;}return pa;}}return NULL;
}

这里有点注意说一下,快指针是否可以设计成每次走三步,四步,或更多,答案是不可以,就拿每次走三步举例,当慢指针入环后,如果快慢指针相差奇数个节点,而两者的距离是每走一步,减少2所以快指针会跳过慢指针,从而无法重叠,再加上如果环的节点是奇数个,快慢指针就永远不会重叠,所以快指针每次走两步是最妥当的。
(2)转化法,这个方法就是将快慢指针相遇节点的next置为空,然后模仿两链表相交找交点的形式,寻找入环第一点,代码如下:

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
struct ListNode *detectCycle(struct ListNode *head) {struct ListNode * fast = head;struct ListNode * slow = head;if(!head){return NULL;}while(fast&&fast->next){fast = fast->next->next;slow = slow->next;if(slow==fast){struct ListNode* curA = head;struct ListNode* tran= slow->next;struct ListNode* curB = slow->next;slow->next = NULL;int lenA = 1;int lenB = 1;while(curA->next){lenA++;curA = curA->next;}while(curB->next){lenB++;curB = curB->next;}struct ListNode* shortLine = head;struct ListNode *longLine = tran;if(lenA>lenB){shortLine = tran;longLine = head;}int count = abs(lenA-lenB);while(count--){longLine = longLine->next;}while(shortLine!=longLine){longLine = longLine->next;shortLine = shortLine->next;}return longLine;}}return NULL;
}

9.复制带随机指针的链表

构造这个链表的深拷贝
这道题的解决方法分三步走:(1)复制链表的每个节点于原节点后;(2)通过原节点寻找其随机指针的指向,如此复制节点的随机指针为原节点随机指针的next;(3)分离出复制节点,形成新链表,解决代码如下:

/*** Definition for a Node.* struct Node {*     int val;*     struct Node *next;*     struct Node *random;* };*/struct Node* copyRandomList(struct Node* head)
{struct Node* cur = head;struct Node* next = NULL;struct Node* newnode = NULL;//复制新节点while(cur){next = cur->next;newnode = (struct Node*)malloc(sizeof(struct Node));newnode->val = cur->val;cur->next = newnode;newnode->next = next;cur = next;}cur = head;//给予新节点自由指针while(cur){newnode = cur->next;if(cur->random)newnode->random = cur->random->next;elsenewnode->random = NULL;cur = newnode->next;}//剥离新节点,形成新链表,恢复旧链表struct Node* guard = (struct Node*)malloc(sizeof(struct Node));guard->next = NULL;struct Node* tail = guard;cur = head;while(cur){newnode = cur->next;next = newnode->next;tail->next = newnode;tail = tail->next;cur->next = next;cur = next;}struct Node* Head = guard->next;free(guard);return Head;
}

结语

以上就是本篇文章的所有内容,本篇内容旨在分享和记录链表问题的解决思路,如有不同看法或者经典题目,欢迎DD我。

两种链表的实现以及例题思路分享相关推荐

  1. win2012命令计算机,Windows2012重启的两种方法:cmd命令关机重启分享

    分享者:iweb2020  阅读量:3748 小金子学院目录最新收录:投资寓言故事之鸡的故事 D 囻 囼9困 囱 囲㊣男 Windows2012重启的两种方法:cmd命令关机重启分享 一,window ...

  2. FP与IP作为两种编程范型的解决问题思路及其适用领域分析

    为什么80%的码农都做不了架构师?>>>    FP的解构模型是计算.它的原则是,一切都可以转化成某种计算.But, is it realy so? IP的模型则是状态演变.计算是一 ...

  3. iOS开发 - 商品详情页两种分页模式,只提供思路和实现方式。

    上面的效果是商品详情页常用的两种模式,分页和不分页.首先请忽略博主懒得去写界面,真正的效果见下面: 不分页模式 分页模式 然后先来依次说明下原理: 分页模式:看着和下拉刷新上拉加载的时候像不像?没错, ...

  4. 移动端怎么让底部固定_移动端排名应该怎么做?两种匹配移动端实战排名干货分享...

    关于移动端优化的问题.最近一些兄弟一直在问我应该怎么做?毕竟现在是手机的时代.绝大部分情况下.PC显得有点鸡肋!在讲移动端排名之前.逆冬先来讲两个容易被大家搞错的问题(移动端). 1.逆冬老师你好.我 ...

  5. 移动端怎么让底部固定_移动端排名应该怎么做?两种匹配移动端实战排名干货分享!...

    关于移动端优化的问题.最近一些兄弟一直在问我应该怎么做?毕竟现在是手机的时代.绝大部分情况下.PC显得有点鸡肋!在讲移动端排名之前.逆冬先来讲两个容易被大家搞错的问题(移动端). 1.我观察现在的移动 ...

  6. 智能电视聚好看连接服务器失败,海信电视不能使用当贝市场的两种解决办法,亲测推荐分享...

    最近,很多海信电视的用户反映,海信智能电视不能安装当贝市场,提示"此安装包未经过安全检测",就连恢复出厂设置再安装也不想,该怎么办? 下面为大家分享海信电视怎么安装软件看电视直播教 ...

  7. 原生JS仿造华为商城案例-实现了简单页面-两种轮播图思路的实现-动态展示数据

    文章目录 功能简介 项目准备 项目说明 方案一:源代码 CSS样式 HTML结构 JS逻辑 方案二:源代码 CSS样式 HTML结构 JS逻辑 功能简介 页面展示 该项目功能实现: 无限滚动轮播图 j ...

  8. 剑指 Offer 52. 两个链表的第一个公共节点(C语言)

    *输入两个链表,找出它们的第一个公共节点. 如下面的两个链表: 在节点 c1 开始相交. 示例 1: 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = ...

  9. (补)20200328:两两交换链表中的节点(leetcode24)

    两两交换链表中的节点 题目 思路与算法 代码实现 复杂度分析 题目 思路与算法 我们以给的例子1→2→3→4说明,因为只需要两两交换,因此最后得到2→1→4→3. 我们把过程写一下:1→2→3→4 到 ...

最新文章

  1. 系统启动时,spring配置文件解析失败,报”cvc-elt.1: 找不到元素 'beans' 的声明“异常...
  2. MSMQ 远程计算机不可用 remotemachinenotavailable
  3. SSL协议(HTTPS) 握手、工作流程详解(双向HTTPS流程)
  4. springboot----静态页面templates文件访问
  5. 关于内容分发网络 CDN 的可靠性和冗余性
  6. H3C Navigate 2017 | 拉近世界的距离 新华三的泛联接版图
  7. Rem布局的原理解析
  8. 神州租车接盘方出现了, 股价收盘涨23%
  9. matlab数学建模试卷,matlab数学建模习题
  10. 开源的物理引擎_开源物理引擎
  11. Qt5 与OpenCV4教程一:Qt5.12安装与OpenCV4.5.0配置
  12. allgro pcb铜皮编辑_干货技巧-Allegro如何设置整体铜皮连接或设置单个管脚连接方式...
  13. Typo Forum
  14. java求4位会员卡号之和中奖,日常作业2018.12.25
  15. 解决data too long for column 'name' at row2
  16. (Emitted value instead of an instance of Error)
  17. 保护计算机组件免受esd,USB3.0接口的ESD防护设计
  18. tensorrt部署YOLOv5模型记录【附代码,支持视频检测】
  19. 网站策划文案-论坛系统模块简介
  20. c语言函数可视化,求在已经完成的c语言程序《万年历》中添加可视化效果

热门文章

  1. JAVA时间日期处理类,主要用来遍历两个日期之间的每一天
  2. Vision Transformer 必读系列之图像分类综述(二): Attention-based
  3. 物流快递电子面单HTML接口API代码-快递100
  4. npm 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
  5. 【论文汇总】2020上半年阿里、腾讯、百度入选AI顶会论文(附地址)
  6. ROS发布静态tf变换
  7. python怎么表白源码_Python浪漫表白源码(附带详细教程)-Go语言中文社区
  8. P3644 [APIO2015]八邻旁之桥(中位数、堆)
  9. 什么事css+hack,css hack的理解
  10. Unity DOTS简明教程