赞
踩
在使用顺序表的过程中,我们能感受到顺序表有以下几个缺点
1)空间不够,需要扩容。扩容(异地)是有一定代价的,其次还可能存在一定的空间浪费。
2)头部或者中部的插入删除,需要挪动数据,效率低下。
面对以上几个问题,我们引入链表结构来解决。
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。
链表的逻辑结构:
链表的现实结构:
注意:
从图中我们可以看出,链表在逻辑结构中是连续的,但在物理结构上链表不一定连续。
链表的结点一般是从堆中申请,而申请的空间不一定连续。
链表的结构非常繁多,但实际应用中我们只能用到两类链表:
单向不带头不循环链表:
双向带头循环链表:
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
单向不带头不循环链表:
- typedef int SLDataType;
- //单链表
- typedef struct SListNode
- {
- SLDataType data;
- struct SListNode* next;
- }SLTNode;
-
- //创建节点
- SLTNode* BuySLTNode(SLDataType x);
- //向链表中填入数据
- SLTNode* CreateSList(int n);
- //打印链表
- void SLTPrint(SLTNode* phead);
- //尾插尾删
- void SLTPushBack(SLTNode** pphead, SLDataType x);
- void SLTPopBack(SLTNode** pphead);
- //头插头删
- void SLTPushFront(SLTNode** pphead,SLDataType x);
- void SLTPopFront(SLTNode* pphead);
- //查找节点
- SLTNode* SLTFind(SLTNode* phead, SLDataType x);
- //单链表在pos位置后插入数据
- void SLTInsertAfter(SLTNode* pos, SLDataType x);
- //单链表删除pos后一位的数据
- void SLTEraseAfter(SLTNode* pos);
- //单链表在pos之前插入数据
- void SLTInsert(SLTNode** pphead,SLTNode* pos,SLDataType x);
- //单链表删除pos位置的数据
- void SLTErase(SLTNode** pphead,SLTNode* pos);
- //释放单链表空间
- void SLTDestroy(SLTNode** pphead);

在实现功能前,我们需要弄懂为什么有的函数传的参数不一样?
仔细观察,在一些函数中传二级指针,都有一个改头结点的操作,那为什么改头结点需要使用二级指针?
引例:
- void Swap1(int* p1, int* p2)
- {
- int tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
- void Swap2(int** pp1, int** pp2)
- {
- int* tmp = *pp1;
- *pp1 = *pp2;
- *pp2 = tmp;
- }
- void Test()
- {
- int a = 0, b = 1;
- int* ptr1 = &a, * ptr2 = &b;
- //交换a与b
- //即a原本为0,b为1,交换后a为1,b为0
- Swap1(&a, &b);
- //交换ptr1与ptr2
- //即ptr1原本指向a,ptr2指向b,交换后ptr1指向b,ptr2指向a
- Swap2(&ptr1, &ptr2);
- }

众所周知,在函数中,对于形参的修改不会影响到实参,会导致函数不会起到预期的作用,因此对于修改实参,我们采用指针的方法。
同理,头结点的类型是SLTNode*,如果我们使用SLTNode*类型的指针修改头结点的指向,形参的改变不会影响实参,头结点的指向不会发生改变,而对于SLTNode*类型的修改,我们采用二级指针来修改它的指向,因此传的参数为SLTNode**类型。
- //创建节点
- SLTNode* BuySLTNode(SLDataType x)
- {
- SLTNode* newnode = malloc(sizeof(SLTNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- exit(-1);
- }
- newnode->data = x;
- newnode->next = NULL;
- return newnode;
- }
-
- //创建链表(将节点链接成链表)
- SLTNode* CreateSList(int n)
- {
- //phead用于找到头节点 ptail用于链接节点
- SLTNode* phead = NULL, *ptail = NULL;
- int x = 0;
- for (int i = 0; i < n; i++)
- {
- //scanf("%d", &x);
- //SLTNode* newnode = BuySLTNode(x);//创建节点
- SLTNode* newnode = BuySLTNode(i);
- if (phead == NULL)
- {
- ptail = phead = newnode;
- }
- else
- {
- ptail->next = newnode;//链接新的节点
- ptail = newnode;//让ptail成为下一个节点
- }
- }
- return phead;
- }
-
- //打印链表
- void SLTPrint(SLTNode* phead)
- {
- SLTNode* cur = phead;
- while (cur != NULL)
- {
- printf("%d->", cur->data);
- //打印整个节点
- /*printf("[%d|%p]->", cur->data, cur->next);*/
- cur = cur->next;//将下一个节点的地址赋值给cur
- }
- printf("NULL\n");
- }
-
- //尾插
- void SLTPushBack(SLTNode** pphead, SLDataType x)
- {
-
- SLTNode* newnode = BuySLTNode(x);
- if (*pphead == NULL)
- {
- //将Plist指向newnode,即将空表的表头链接节点
- *pphead = newnode;
- }
- else
- {
- SLTNode* tail = *pphead;
- //找尾
- while (tail->next)
- {
- tail = tail->next;
- }
- //链接
- tail->next = newnode;
- }
- }
-
- //尾删
- void SLTPopBack(SLTNode** pphead)
- {
- assert(*pphead);//表不能为空
-
- if ((*pphead)->next == NULL)//表中只剩一个节点
- {
- free(*pphead);
- *pphead = NULL;
- }
- else
- {
- //方法一:
- //SLTNode* tail = *pphead;
- //SLTNode* prev = NULL;//找到尾删的前一项
- 找尾
- //while (tail->next)
- //{
- // prev = tail;
- // tail = tail->next;
- //}
- //free(tail);
- //prev->next = NULL;
-
- //方法二:
- SLTNode* tail = *pphead;
- //找尾
- while (tail->next->next)
- {
- tail = tail->next;
- }
- free(tail->next);
- tail->next = NULL;
- }
-
- }
-
- //头插
- void SLTPushFront(SLTNode** pphead, SLDataType x)
- {
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = *pphead;//将节点与单链表链接
- *pphead = newnode;//让新节点成为头节点
- }
-
- //头删
- void SLTPopFront(SLTNode** pphead)
- {
- assert(*pphead);
- SLTNode* next = (*pphead)->next;//找到表中第二个节点
- free(*pphead);//释放删除节点的空间
- *pphead = next;//让第二个节点成为头节点
- }
-
- //查找节点
- SLTNode* SLTFind(SLTNode* phead, SLDataType x)
- {
- SLTNode* cur = phead;
- while (cur)
- {
- if ((cur->data) == x)
- {
- return cur;
- }
- cur = cur->next;
- }
- return NULL;
- }
-
- //单链表在pos位置后插入数据
- void SLTInsertAfter(SLTNode* pos, SLDataType x)
- {
- assert(pos);
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = pos->next;//先将pos的后一位链接到新节点
- pos->next = newnode;//将新节点链接到pos上
- }
-
- //单链表删除pos后一位的数据
- void SLTEraseAfter(SLTNode* pos)
- {
- assert(pos);
- if (pos->next == NULL)
- {
- return;
- }
- else
- {
- SLTNode* prev = pos->next;//保存pos后一位的地址
- pos->next = prev->next;//将pos与prev的下一个节点链接
- free(prev);//释放pos后一位节点空间
- }
- }
-
- //单链表在pos之前插入数据
- void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
- {
- assert(pos);
- if (*pphead == pos)//当pos为表头就是头插
- {
- SLTPushFront(pphead, x);
- }
- else
- {
- SLTNode* prev = *pphead;//保存pos前一节点的地址
- while (prev->next != pos)//寻找pos前一节点
- {
- prev = prev->next;
- }
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = pos;//链接节点-后
- prev->next = newnode;//链接节点-前
- }
- }
-
- //单链表删除pos位置的数据
- void SLTErase(SLTNode** pphead, SLTNode* pos)
- {
- assert(pos);
- assert(*pphead);
- if (*pphead == pos)//当pos为表头就是头删
- {
- SLTPopFront(pphead);
- }
- else
- {
- SLTNode* prev = *pphead;//保存pos前一节点
- while (prev->next != pos)//寻找pos前一节点
- {
- prev = prev->next;
- }
- prev->next = pos->next;//删除pos节点,链接前后节点
- free(pos);//释放pos节点空间
- }
- }
-
- //释放单链表空间
- void SLTDestroy(SLTNode** pphead)
- {
- SLTNode* cur = *pphead; //替身指针 (释放节点)
- while (cur)
- {
- SLTNode* next = cur->next; //保存释放空间的下一个节点地址
- free(cur);//释放链表节点
- cur = next;//释放下一个节点
- }
- *pphead = NULL;//将链表表头指针置为NULL,防止再次错误调用。(野指针)
- }

相比于顺序表,单链表有优点,相对的这种数据结构也有缺点
优点:头插头删,时间复杂度为O(1)。
缺点:当我们想要在任意位置插入删除数据,需要记录他的前一个节点,这就需要去寻找这个节点,需要遍历一遍整个链表,因此时间复杂度为O(N)。
既然单链表这种数据结构有这种缺陷,那是否有4方法来解决这个缺点?
对此我们引入带头双向循环链表这种数据结构来解决整个问题。
- typedef int DLDataType;
- typedef struct ListNode
- {
- struct ListNode* next;
- struct ListNode* prev;
- DLDataType data;
- }DLTNode;
- //创建节点
- DLTNode* BuyNode(DLDataType x);
- //初始化头节点
- DLTNode* DListInit();
- //打印聊表:
- void DListPrint(DLTNode* pHead);
- //尾插尾删
- void DListPushBack(DLTNode* pHead,DLDataType X);
- void DListPopBack(DLTNode* pHead);
- //头插头删
- void DListPushFront(DLTNode* pHead,DLDataType x);
- void DListPopFront(DLTNode* pHead);
- //查找
- DLTNode* DLFind(DLTNode* pHead,DLDataType x);
- //任意位置插入删除
- void DLInsert(DLTNode* pos, DLDataType* x);//pos前
- void DLErase(DLTNode* pos);
- //判断链表为空
- bool DLEmpty(DLTNode* pHead);
- //链表数据个数
- size_t DLSize(DLTNode* pHead);
- //销毁链表
- void DLDestroy(DLTNode* pHead);

为什么在这个链表中,函数的参数没有使用二级指针?
在实现这个链表时,我们使用哨兵位的头结点,因此在链接或删除结点时,仅仅改变结点中的next或者prev指针,实际上就是改变结构体,使用结构体指针就可以,不需要使用二级指针。
- //创建节点
- DLTNode* BuyNode(DLDataType x)
- {
- DLTNode* newnode = (DLTNode*)malloc(sizeof(DLTNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- exit(-1);
- }
- newnode->next = NULL;
- newnode->prev = NULL;
- newnode->data = x;
- return newnode;
- }
- //初始化头节点
- DLTNode* DListInit()
- {
- DLTNode* pHead = BuyNode(-1);
- pHead->next = pHead;
- pHead->prev = pHead;
-
- return pHead;
- }
- //打印链表
- void DListPrint(DLTNode* pHead)
- {
- assert(pHead);
- DLTNode* cur = pHead->next;
- while (cur != pHead)
- {
- printf("-%d", cur->data);
- cur = cur->next;
- }
- printf("\n");
- }
- //尾插尾删
- void DListPushBack(DLTNode* pHead, DLDataType x)
- {
- assert(pHead);
- //DLTNode* newnode = BuyNode(x);
- //DLTNode* tail = pHead->prev;//找到尾节点
-
- //tail->next = newnode;//与前一节点双向链接
- //newnode->prev = tail;
-
- //newnode->next = pHead;//与头节点双向链接
- //pHead->prev = newnode;
- DLInsert(pHead, x);
- }
- void DListPopBack(DLTNode* pHead)
- {
- assert(pHead);
- assert(pHead->next != pHead);
- //DLTNode* tail = pHead->prev;//找到尾节点
- //DLTNode* tailprev = tail->prev;//找到尾节点的前节点
- //pHead->prev = tailprev;//将前节点与头节点双向链接
- //tailprev->next = pHead;
- //free(tail);
- DLErase(pHead->prev);
- }
- //头插头删
- void DListPushFront(DLTNode* pHead, DLDataType x)
- {
- assert(pHead);
- //DLTNode* newnode = BuyNode(x);
- //DLTNode* tail = pHead->next;//找到头节点下一位
- //newnode->next = tail;//将插入节点与tail相连
- //tail->prev = newnode;
-
- //newnode->prev = pHead;//将插入节点与头节点相连
- //pHead->next = newnode;
- DLInsert(pHead->next, x);
- }
- void DListPopFront(DLTNode* pHead)
- {
- assert(pHead);
- assert(pHead->next != pHead);
- //DLTNode* cur = pHead->next;//找到删除节点
- //DLTNode* next = cur->next;//找到删除节点的下一位
-
- //pHead->next = next;//链接头节点与下一位节点
- //next->prev = pHead;
- //free(cur);
- DLErase(pHead->next);
- }
- //查找
- DLTNode* DLFind(DLTNode* pHead,DLDataType x)
- {
- assert(pHead);
- DLTNode* cur = pHead->next;
- while(cur != pHead)
- {
- if (cur->data == x)
- {
- return cur;
- }
- cur = cur->next;
- }
- return NULL;
- }
- //任意位置插入删除
- void DLInsert(DLTNode* pos, DLDataType* x)
- {
- assert(pos);
- DLTNode* newnode = BuyNode(x);
- DLTNode* posprev = pos->prev;//pos的前一节点
- //链接三个节点 posprev newnode pos
- posprev->next = newnode;//新节点与前节点链接
- newnode->prev = posprev;
-
- newnode->next = pos;//新节点与pos位置链接
- pos->prev = newnode;
- }
- void DLErase(DLTNode* pos)
- {
- assert(pos);
- DLTNode* posprev = pos->prev;//找到前一个节点
- DLTNode* posnext = pos->next;//找到后一个节点
- //链接 posprev posnext
- posprev->next = posnext;
- posnext->prev = posprev;
- free(pos);
- }
- //判断链表为空
- bool DLEmpty(DLTNode* pHead)
- {
- assert(pHead);
- /*if (pHead->next == pHead)
- {
- return true;
- }
- else
- return false;*/
- return pHead->next == pHead;
- }
- //链表数据个数
- size_t DLSize(DLTNode* pHead)
- {
- assert(pHead);
- size_t sum = 0;
- DLTNode* cur = pHead->next;
- while (cur != pHead)
- {
- sum++;
- cur = cur->next;
- }
- return sum;
- }
- //销毁链表
- void DLDestroy(DLTNode* pHead)
- {
- assert(pHead);
- DLTNode* cur = pHead->next;
- while (cur != pHead)
- {
- DLTNode* next = cur->next;
- free(cur);
- cur = next;
- }
- free(pHead);
- }

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
思路:创建一个新的头结点,遍历源链表,将不等于val的节点链接到创建的新节点后
代码实现:
- struct ListNode* removeElements(struct ListNode* head, int val)
- {
- struct ListNode* rhead, *tail;
- tail = rhead = (struct ListNode*)malloc(sizeof(struct ListNode));//哨兵位
- tail->next = NULL;
- struct ListNode* cur = head;
- while(cur)
- {
- struct ListNode* next = cur->next;//记录下一节点
- if(cur->val != val)
- {
- tail->next = cur;//链接到新节点
- cur->next = NULL;//将已链接的节点与原链表断开
- tail = tail->next;
- }
- cur = next;
- }
- return rhead->next;
- }

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
思路1:遍历原链表,将数据头插到新链表
- struct ListNode* reverseList(struct ListNode* head)
- {
- struct ListNode* cur = head;
- struct ListNode* rhead = NULL;
- while(cur)
- {
- struct ListNode* next = cur->next;//保留下一个节点
- //头插
- cur->next = rhead;
- rhead = cur;
- cur = next;
- }
- return rhead;
- }
思路2:三指针迭代,将链表反转
- struct ListNode* reverseList(struct ListNode* head) {
- if (head == NULL || head->next == NULL)//链表为空,或者就一个元素
- return head;
- struct ListNode* prev = NULL, * cur = head, * next = head->next;
- while (cur)
- {
- //反转链表
- cur->next = prev;
- //迭代
- prev = cur;
- cur = next;
- if (next)
- next = next->next;
- }
- return prev;
- }

给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
思路:设置快慢指针,快指针一次走两部,慢指针走一步,奇数个和偶数个时情况不同。
- struct ListNode* middleNode(struct ListNode* head)
- {
- struct ListNode* fast, *slow;
- fast = slow = head;
- while(fast && fast->next)
- {
- slow = slow->next;
- fast = fast->next->next;
- }
- return slow;
- }
输入一个链表,输出该链表中倒数第k个结点。
思路:设置快慢指针,通过观察,让快指针先走k步,然后快指针和慢指针同时走,当快指针为空时,慢指针所在位置即为倒数第k位。
- struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
- {
- struct ListNode* fast,*slow;
- fast = slow = pListHead;
- while(k--)
- {
- //链表没有k步
- if(fast == NULL)
- {
- return NULL;
- }
- fast = fast->next;
- }
-
- while(fast)
- {
- slow = slow->next;
- fast = fast->next;
- }
- return slow;
- }

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
思路:新建一个头结点,遍历两条链表,并比较其val大小,小的链接到新链表中。
- struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
- {
- struct ListNode* guard,*tail;
- guard = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
- guard ->next = NULL;
- while(list1 && list2)
- {
- if(list1->val < list2->val)
- {
- tail->next = list1;
- tail = tail->next;
- list1 = list1->next;
- }
- else
- {
- tail->next = list2;
- tail = tail->next;
- list2 = list2->next;
- }
- }
- if(list1)//第一条剩余
- {
- tail->next = list1;
- }
- if(list2)//第二条剩余
- {
- tail->next = list2;
- }
- struct Node* head = guard->next;//返回哨兵的下一位才是链表
- free(guard);
-
- return head;
- }

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
思路:新创建两个链表,一个用来链接小于x的,另一个用来链接大于x的,然后将两条链表链接,将合并的链表链接到原链表头节点。
- class Partition {
- public:
- ListNode* partition(ListNode* pHead, int x) {
- struct ListNode* cur = pHead;
- struct ListNode* lessHead,*lessTail,*greaterHead,*greaterTail;
- lessTail = lessHead = (ListNode*)malloc(sizeof(ListNode));
- greaterTail = greaterHead = (ListNode*)malloc(sizeof(ListNode));
- lessTail->next = NULL;
- greaterTail->next = NULL;
- while(cur)
- {
- if(cur->val<x)
- {
- lessTail->next = cur;
- lessTail = lessTail->next;
- }
- else
- {
- greaterTail->next = cur;
- greaterTail = greaterTail->next;
- }
- cur = cur->next;
- }
- lessTail->next = greaterHead->next;
- greaterTail->next = NULL;
- pHead = lessHead->next;
- free(lessHead);
- free(greaterHead);
- return pHead;
- }
- };

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
思路:先找到链表的中间位置的节点,然后将中间节点以后的节点逆置,创建一个新节点,然后将逆置后的链表链接到新节点后,比较原链表与新链表,若前半段相同,则为回文。
- class PalindromeList {
- public:
- struct ListNode* middleNode(struct ListNode* head) {
- struct ListNode* fast, *slow;
- fast = slow = head;
- while (fast && fast->next) {
- slow = slow->next;
- fast = fast->next->next;
- }
- return slow;
- }//找中间功能
-
- struct ListNode* reverseList(struct ListNode* head) {
- struct ListNode* cur = head;
- struct ListNode* rhead = NULL;
- while (cur) {
- struct ListNode* next = cur->next;//保留下一个节点
- //头插
- cur->next = rhead;
- rhead = cur;
- cur = next;
- }
- return rhead;
- }//逆置功能
-
- bool chkPalindrome(ListNode* A) {
- // write code here
- struct ListNode* mid = middleNode(A);
- struct ListNode* rhead = reverseList(mid);
- while(A && rhead)
- {
- if(A->val != rhead->val)
- {
- return false;
- }
- A = A->next;
- rhead = rhead->next;
- }
- return true;
- }
- };

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
思路:首先判断两条链表是否相交,即判断链表的最后一个节点地址是否相同,求出两条链表的长度,设置两个指针用来遍历链表,求出长度的差值,让长的链表的指针先走差值的步数,然后两个指针同时走,俩指针相等,即为交点。
- struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
- {
- struct ListNode* curA = headA;
- struct ListNode* curB = headB;
- int lenA = 0, lenB = 0;
- //求得A、B链的长度
- 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* longlist = headA, *shortlist = headB;
- if(lenA < lenB)
- {
- shortlist = headA;
- longlist = headB;
- }
- //长链先遍历差距步
- while(gap--)
- {
- longlist = longlist->next;
- }
- //同时遍历
- while(longlist != shortlist)
- {
- longlist = longlist->next;
- shortlist = shortlist->next;
- }
- return longlist;
- }

给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
思路:设置快慢,快指针一次走两步,慢指针一次走一步,当慢指针入环时,问题转化为快指针追赶慢指针,根据相对运动,两个指针每走一次,相对距离就会减小一,快指针就会离慢指针更近一步,最终二者在环中相遇,即为环,如果不是环,快指针一定先走到末端。
- bool hasCycle(struct ListNode *head)
- {
-
- struct ListNode* fast = head, *slow = head;
- while(fast && fast->next)
- {
-
- fast = fast->next->next;
- slow = slow->next;
- if(fast == slow)
- {
- return true;
- }
- }
- return false;
- }

扩展问题:
为什么快指针每次走两步,慢指针走一步可以?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在满指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。
快指针一次走3步,走4步,...n步行吗?
不一定,假设快指针一次走3步,慢指针在入环后,快指针以相对2个单位追赶慢指针,如果N为奇数,N为俩指针之间的距离,则他俩之间的距离变化为:N-2,N-4,.........3,1,-1.当变为-1时,则又开始新一轮的追击,而他俩之间的距离为C-1,C为整个环的周长,如果C-1为偶数,则可以追赶上,若为奇数,则追不上。
由以上可知,只要fast与slow的相对速度为1,无论如何都可以追上。
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
思路:
我们观察可以看出,这一类问题可总结出规律:
假设进环前长度为L,环的周长为C,进环点与相遇点的距离为N
公式推导:
fast走的距离 = 2*slow走的距离
slow走的距离 = L+N
fast走的距离 = L+n*C+N(slow进环前,fast已经走了n圈)
为什么slow走的不是L+n*C+X呢? 即为什么slow在圈里一定走了不到一圈就相遇了呢?我们知道当slow刚刚进圈时slow与fast之间的距离一定小于C-1,fast一次走两步,slow一次走一步,距离逐渐减小,即一定走了小于C-1次就会相遇,因此推出此时slow走了不到一圈。
则 2*(L+N) = L+n*C+N,即L = n*C-N
得到:
由此可以,一个指针在头结点,一个指针在相遇点,两指针同时走,一定可以相遇,相遇点即为进环点。
同时问题也可以转化成相交问题,我们可以创建一个指针记录相遇点meet的后一位节点,然后令其与相遇点断开,那么问题就变成了链链表的相交问题。
公式法:
- struct ListNode *detectCycle(struct ListNode *head) {
- struct ListNode* fast = head, *slow = head;
- while(fast && fast->next)
- {
-
- fast = fast->next->next;
- slow = slow->next;
- if(fast == slow)
- {
- struct ListNode* meet = slow;
- while(head != meet)
- {
- head = head->next;
- meet = meet->next;
- }
- return meet;
- }
- }
- return NULL;
- }

相交法:
- struct ListNode* hasCycle(struct ListNode *head)
- {
-
- struct ListNode* fast = head, *slow = head;
- while(fast && fast->next)
- {
-
- fast = fast->next->next;
- slow = slow->next;
- if(fast == slow)
- {
- return slow;
- }
- }
- return NULL;
- }
-
- struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
- {
- struct ListNode* curA = headA;
- struct ListNode* curB = headB;
- int lenA = 0, lenB = 0;
- //求得A、B链的长度
- 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* longlist = headA, *shortlist = headB;
- if(lenA < lenB)
- {
- shortlist = headA;
- longlist = headB;
- }
- //长链先遍历差距步
- while(gap--)
- {
- longlist = longlist->next;
- }
- //同时遍历
- while(longlist != shortlist)
- {
- longlist = longlist->next;
- shortlist = shortlist->next;
- }
- return longlist;
- }
- struct ListNode *detectCycle(struct ListNode *head) {
-
- struct ListNode* meet = hasCycle(head);
- if(meet== NULL)
- {
- return NULL;
- }
- struct ListNode* rhead = meet->next;//记录相遇点的下一节点
- meet->next = NULL;//断开环,防止进入死循环
- struct ListNode* cur = head;
- struct ListNode* meetagain = getIntersectionNode(cur,rhead);
- meet->next = rhead;//恢复环
- return meetagain;
- }

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。
思路:
方法一:
通过暴力的方法来查找random指针,通过i来记录random指针指向的节点位于整个链表的第几位,这种方法首先需要挨个遍历每个节点,找到节点random指针的指向,并用i来记录该节点是链表的第几个,从而当处理拷贝链表的过程中,再利用双层循环将每个特定的位置的random指向这个拷贝链表相对应的位置。
方法二:
1.将链表的每一个节点拷贝出来,并链接到原节点的后一为
2.设置拷贝节点的random指针,拷贝节点的random指针指向的就是原节点的random指针指向的节点的后一节点(拷贝节点就在原节点后)。
3.将拷贝节点与原节点断开,并链接,恢复原链表。
- struct Node* copyRandomList(struct Node* head)
- {
- //将链表每个节点拷贝并链接到本体后
- struct Node* cur = head;
- while(cur)
- {
- struct Node* next = cur->next;
- struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
- copy->val = cur->val;
- //链接拷贝节点
- cur->next = copy;
- copy->next = next;
-
- cur = next;
- }
- //设置拷贝节点的random指针
- cur = head;
- while(cur)
- {
- struct Node* copy = cur->next;
-
- if(cur->random == NULL)
- {
- copy->random = NULL;
- }
- else
- {
- copy->random = cur->random->next;
- }
-
- cur = copy->next;
- }
- //将拷贝节点链接
- cur = head;
- struct Node* copyHead = NULL, *copyTail=NULL;
- while(cur)
- {
- struct Node* copy = cur->next;
- struct Node* next = copy->next;
-
- cur->next = next;
- if(copyTail == NULL)
- {
- copyTail = copyHead = copy;
- }
- else
- {
- copyTail->next =copy;
- copyTail = copyTail->next;
- }
- cur = next;
- }
- return copyHead;
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。