初阶数据结构——单链表与双向链表及其应用
- 链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次实现的。
链表的结构跟火车车厢相似,淡季时车厢会相应减少,旺季时车厢会额外增加。将火车中的的某节车厢去掉或加上,不会影响其他车厢,每节车厢都是独立存在的。
车厢是独立存在的,且每节车厢都有车门。假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下,我们想要从车头走向车位,最简单的情况就是:每节车厢里都放一把下一届车厢的钥匙。
对应到我们的链表中,与顺序表不同的是,链表中的每节"车厢"都是独立申请的空间,我们称之为“节点” ,节点的组成主要有两个部分:当前节点保存的数据和下一个节点的指针。
注意:链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),所以我们需要通过指针变量来保存下一个节点位置,这样我们才能从当前节点找到下一个节点。
因此,我们可以给出每个节点对应的结构体代码:
struct SListNode
{
int data; //存储的数据
struct SLTNode* pnext; //下一个节点的地址
};
- 单链表的实现
- 单链表接口实现:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType;
typedef struct SListNode SLTNode;
struct SListNode
{
SLTDataType data; //存储的数据
SLTNode* pnext; //下一个节点的地址
};
//单链表打印
void SLTPrint(SLTNode* phead);
//空间申请
SLTNode* SLTMalloc(SLTDataType x);
//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode * *pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
//查找函数
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
- 单链表部分函数的实现
- 单链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x)
{
assert(pphead);
SLTNode* NewNode = MallocNewSTNode(x);
if (*pphead == NULL)
{
*pphead = NewNode;
}
else
{
SLTNode* pcur = *pphead;
while (pcur->pnext != NULL)
{
pcur = pcur->pnext;
}
pcur->pnext = NewNode;
}
}
先要判断该单链表是否为空链表,两种情况需要分类讨论。如果该链表为空链表,直接将申请好的节点赋予头节点即可。若不是空链表,那么就需要通过pcur指针寻找链表的尾节点(插入之前),并且让pcur停留在尾节点处,将尾节点的pnext指针赋予新申请的节点空间即可。
- 单链表的尾删
//单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
assert(*pphead && pphead);
if ((*pphead)->pnext == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* pcur = (*pphead)->pnext;
SLTNode* prev = *pphead;
while (pcur->pnext != NULL)
{
pcur = pcur->pnext;
prev = prev->pnext;
}
free(pcur);
pcur = NULL;
prev->pnext = NULL;
}
}
先要判断该单链表是否只存在一个节点(即头节点),还是存在多个节点。如果只有一个头节点,那么直接将头节点释放并置空即可。如果存在多个节点,需要先找到尾节点(删除之前)与尾节点之前的前置节点。释放并置空尾节点后,将前置节点的pnext指针置空。
- 删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (pos == *pphead)
{
SLTNode* pcur = *pphead;
*pphead = pcur->pnext;
free(pcur);
pcur = NULL;
}
else
{
SLTNode* prev = *pphead;
while (prev->pnext != pos)
{
prev = prev->pnext;
}
prev->pnext = pos->pnext;
free(pos);
pos = NULL;
}
}
先要判断该pos是否为头节点(即*pphead==pos),还是其他节点。如果该节点为pos节点,将头节点向后移动后,直接释放pos节点即可。若为其他节点,需要通过头节点找到pos节点的前置节点prev节点,让prev节点的pnext指针指向pos节点的后置节点并释放pos节点即可。
注意:在链表中,我们通常需要考虑链表为空或者操作头节点的特殊情况。
- 完整接口实现
#include"SList.h"
//打印节点
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur != NULL)
{
printf("%d->", pcur->data);
pcur = pcur->pnext;
}
printf("NULL\n");
}
//申请新的节点
SLTNode* MallocNewSTNode(SLTDateType x)
{
SLTNode* NewNode = (SLTNode*)malloc(sizeof(SLTNode));
assert(NewNode);
NewNode->data = x;
NewNode->pnext = NULL;
return NewNode;
}
//单链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x)
{
assert(pphead);
SLTNode* NewNode = MallocNewSTNode(x);
if (*pphead == NULL)
{
*pphead = NewNode;
}
else
{
SLTNode* pcur = *pphead;
while (pcur->pnext != NULL)
{
pcur = pcur->pnext;
}
pcur->pnext = NewNode;
}
}
//单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDateType x)
{
assert(pphead != NULL);
SLTNode* NewNode = MallocNewSTNode(x);//获取一块空间
NewNode->pnext = *pphead;//将新空间的下一个地址变为原来单链表的头地址
*pphead = NewNode;//让新链表的头地址变为新申请空间的地址
}
//单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
assert(*pphead && pphead);
if ((*pphead)->pnext == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* pcur = (*pphead)->pnext;
SLTNode* prev = *pphead;
while (pcur->pnext != NULL)
{
pcur = pcur->pnext;
prev = prev->pnext;
}
free(pcur);
pcur = NULL;
prev->pnext = NULL;
}
}
//单链表的头删
void SLTPopFront(SLTNode** pphead)
{
//链表不能为空
assert(pphead != NULL && *pphead != NULL);
SLTNode* pcur = (*pphead)->pnext;
free(*pphead);
*pphead = pcur;
}
//单链表中元素的查找
SLTNode* SLTFind(SLTNode* phead, SLTDateType x)
{
assert(phead != NULL);
SLTNode* pcur = phead;
while (pcur != NULL)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->pnext;
}
return NULL;
}
//单链表在指定位置之前插入数据(如果pos为头节点,那么插入一个节点,造成头节点的改变)
//所以使用二级指针
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* NewNode = MallocNewSTNode(x);
if (pos == *pphead)
{
NewNode->pnext = *pphead;
*pphead = NewNode;
}
else
{
SLTNode* prev = *pphead;
while (prev->pnext != pos)
{
prev = prev->pnext;
}
prev->pnext = NewNode;
NewNode->pnext = pos;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDateType x)
{
assert(pos != NULL);
SLTNode* NewNode = MallocNewSTNode(x);
NewNode->pnext = pos->pnext;
pos->pnext = NewNode;
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (pos == *pphead)
{
SLTNode* pcur = *pphead;
*pphead = pcur->pnext;
free(pcur);
pcur = NULL;
}
else
{
SLTNode* prev = *pphead;
while (prev->pnext != pos)
{
prev = prev->pnext;
}
prev->pnext = pos->pnext;
free(pos);
pos = NULL;
}
}
//删除pos节点之后的数据
void SLTEraseAfter(SLTNode* pos)
{
assert(pos != NULL);
assert((pos->pnext) != NULL);
SLTNode* pcur = pos->pnext;
pos->pnext = pcur->pnext;
free(pcur);
pcur = NULL;
}
//链表销毁
void SListDesTroy(SLTNode** pphead)
{
assert(pphead != NULL && *pphead != NULL);
SLTNode* pcur = *pphead;
while (pcur != NULL)
{
SLTNode* next = pcur->pnext;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
- 双向链表
- 双向链表的概念:
注意:这里的“带头”跟单链表中我们说的“头节点”是两个概念,实际在单链表阶段称呼不严谨, 带头链表里的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里“放哨的” 。“哨兵位”存在的意义: 遍历循环链表避免死循环。
- 对应节点的结构体代码
typedef int LTDataType;
typedef struct ListNode LTNode;
struct ListNode
{
LTNode* prev; //保存前一个节点的指针
LTDataType data;
LTNode* next; //保存后一个节点的指针
};
- List.h 头文件内容
typedef int LTDataType;
typedef struct ListNode LTNode;
struct ListNode
{
LTNode* prev; //保存前一个节点的指针
LTDataType data;
LTNode* next; //保存后一个节点的指针
};
//申请内存空间
LTNode* LTMalloc(LTDataType x);
//初始化——给链表创造一个哨兵位
LTNode* LTInit();
//打印链表
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//寻找节点
LTNode* LTFindNode(LTNode* phead, LTDataType x);
//在pos节点之后位置插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除pos节点
void LTErease(LTNode* pos);
//销毁链表
void LTDestory(LTNode* phead);
- 完整接口实现
#include"List.h"
//申请内存空间
LTNode* LTMalloc(LTDataType x)
{
LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));
assert(NewNode);
NewNode->next = NewNode;
NewNode->prev = NewNode;
NewNode->data = x;
return NewNode;
}
//初始化——给链表创造一个哨兵位
LTNode* LTInit()
{
LTNode* phead = LTMalloc(EOF);
return phead;
}
//打印链表
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
printf("phead->");
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("phead\n");
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* NewNode = LTMalloc(x);
NewNode->next = phead;
NewNode->prev = phead->prev;
phead->prev->next = NewNode;
phead->prev = NewNode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* NewNode = LTMalloc(x);
NewNode->prev = phead;
NewNode->next = phead->next;
phead->next->prev = NewNode;
phead->next = NewNode;
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead && phead->prev);
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
//寻找节点
LTNode* LTFindNode(LTNode* phead, LTDataType x)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在pos节点之后位置插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* NewNode = LTMalloc(x);
NewNode->next = pos->next;
NewNode->prev = pos;
pos->next->prev = NewNode;
pos->next = NewNode;
}
//删除pos节点
void LTErease(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
//销毁链表
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
- 链表经典OJ题目即解析
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
提示:
- 列表中的节点数目在范围
[0, 104]
内 1 <= Node.val <= 50
0 <= val <= 50
解题代码与思路:
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val)
{
ListNode* newhead=(ListNode*)malloc(sizeof(ListNode));
newhead->next=NULL;
ListNode* newtail=newhead;
ListNode* pcur=head;
while(pcur!=NULL)
{
if(pcur->val!=val)
{
newtail->next=pcur;
newtail=newtail->next;
}
pcur=pcur->next;
}
if(newtail->next!=NULL)
newtail->next=NULL;
return newhead->next;
}
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000]
-5000 <= Node.val <= 5000
解题代码与思路:
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head)
{
if(head==NULL)
{
return NULL;
}
else
{
ListNode* prev=NULL;
ListNode* pcur=head;
ListNode* pnext=head->next;
while(pcur!=NULL)
{
pcur->next=prev;
prev=pcur;
pcur=pnext;
if(pnext!=NULL)
pnext=pnext->next;
}
return prev;
}
}
给你单链表的头结点 head
,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。
示例 2:
输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。
提示:
- 链表的结点数范围是
[1, 100]
1 <= Node.val <= 100
解题代码与思路:
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head)
{
ListNode* slow=head;
ListNode* fast=head;
while(fast!=NULL&&fast->next!=NULL)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
**注意:**本题相对原题稍作改动
示例:
输入: 1->2->3->4->5 和 k = 2
输出: 4
说明:
给定的 k 保证是有效的。
解题代码与思路:
typedef struct ListNode ListNode;
int kthToLast(struct ListNode* head, int k)
{
ListNode* slow=head;
ListNode* fast=head;
//让快指针先跑k个单位
while(k)
{
fast=fast->next;
k--;
}
while(fast!=NULL)
{
fast=fast->next;
slow=slow->next;
}
return slow->val;
}
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
- 两个链表的节点数目范围是
[0, 50]
-100 <= Node.val <= 100
l1
和l2
均按 非递减顺序 排列
解题代码与思路:
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
if(list1==NULL)
return list2;
else if(list2==NULL)
return list1;
else if(list1==NULL&&list2==NULL)
return NULL;
ListNode* newhead=(ListNode*)malloc(sizeof(ListNode));
newhead->next=NULL;
ListNode* newtail=newhead;
ListNode* pcur1=list1;
ListNode* pcur2=list2;
while(pcur1!=NULL&&pcur2!=NULL)
{
if(pcur1->val>pcur2->val)
{
newtail->next=pcur2;
pcur2=pcur2->next;
newtail=newtail->next;
}else
{
newtail->next=pcur1;
pcur1=pcur1->next;
newtail=newtail->next;
}
}
if(pcur1==NULL)
{
newtail->next=pcur2;
}else{
newtail->next=pcur1;
}
return newhead->next;
}
给你一个链表的头节点 head
和一个特定值 x
,请你对链表进行分隔,使得所有 小于 x
的节点都出现在 大于或等于 x
的节点之前。
你不需要 保留 每个分区中各节点的初始相对位置。
示例 1:
输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
示例 2:
输入:head = [2,1], x = 2
输出:[1,2]
提示:
- 链表中节点的数目在范围
[0, 200]
内 -100 <= Node.val <= 100
-200 <= x <= 200
解题代码与思路:
typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x)
{
if(head==NULL)
{
return NULL;
}else{
ListNode* newheadsmall=(ListNode*)malloc(sizeof(ListNode));
ListNode* newheadbig=(ListNode*)malloc(sizeof(ListNode));
newheadsmall->next=NULL;
newheadbig->next=NULL;
ListNode* newtailsmall=newheadsmall;
ListNode* newtailbig=newheadbig;
ListNode* pcur=head;
while(pcur!=NULL)
{
if(pcur->val<x)
{
newtailsmall->next=pcur;
newtailsmall=newtailsmall->next;
pcur=pcur->next;
if(newtailsmall->next!=NULL)
newtailsmall->next=NULL;
}else
{
newtailbig->next=pcur;
newtailbig=newtailbig->next;
pcur=pcur->next;
if(newtailbig->next!=NULL)
newtailbig->next=NULL;
}
}
//到这里已经获得了两个链表,接下来该移动了
ListNode* newheadbig2=newheadbig->next;
ListNode* smallpcur=newheadsmall;
while(smallpcur->next!=NULL)
{
smallpcur=smallpcur->next;
}
smallpcur->next=newheadbig2;
return newheadsmall->next;
}
}
给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
示例 1:
输入:head = [1,2,2,1]
输出:true
示例 2:
输入:head = [1,2]
输出:false
提示:
- 链表中节点数目在范围
[1, 105]
内 0 <= Node.val <= 9
解题代码与思路:
typedef struct ListNode ListNode;
//逆置链表函数
struct ListNode* reverseList(struct ListNode* head)
{
ListNode* prev=NULL;
ListNode* pcur=head;
ListNode* pnext=head->next;
while(pcur!=NULL)
{
pcur->next=prev;
prev=pcur;
pcur=pnext;
if(pnext!=NULL)
pnext=pnext->next;
}
return prev;
}
//寻找中点函数
struct ListNode* middleNode(struct ListNode* head)
{
ListNode* slow=head;
ListNode* fast=head;
while(fast!=NULL&&fast->next!=NULL)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
//回文函数
bool isPalindrome(struct ListNode* head)
{
//找到中点并且拆分成两个链表
ListNode* afhead=middleNode(head);//寻找中点
//afhead为中点
//将af链表逆置
ListNode* newafhead=reverseList(afhead);//逆置后获得后半段链表的新头
ListNode* afpcur=newafhead;
ListNode* bepcur=head;
while(afpcur!=NULL&&bepcur!=NULL)
{
if(afpcur->val!=bepcur->val)
{
return false;
}
afpcur=afpcur->next;
bepcur=bepcur->next;
}
return true;
}
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal
- 相交的起始节点的值。如果不存在相交节点,这一值为0
listA
- 第一个链表listB
- 第二个链表skipA
- 在listA
中(从头节点开始)跳到交叉节点的节点数skipB
- 在listB
中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA
和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
示例 2:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null。
提示:
listA
中节点数目为m
listB
中节点数目为n
1 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
- 如果
listA
和listB
没有交点,intersectVal
为0
- 如果
listA
和listB
有交点,intersectVal == listA[skipA] == listB[skipB]
解题代码与思路:
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
//先判断是否相交
ListNode* pcurA=headA;
ListNode* pcurB=headB;
int lenA=1;
int lenB=1;
while(pcurA->next!=NULL)
{
pcurA=pcurA->next;
lenA++;
}
while(pcurB->next!=NULL)
{
pcurB=pcurB->next;
lenB++;
}
if(pcurA!=pcurB)
{
return NULL;
}
//假设法
ListNode* LongNode=headA;
ListNode* ShortNode=headB;
int gap=fabs(lenA-lenB);
if(lenB>lenA)
{
LongNode=headB;
ShortNode=headA;
}
//快慢指针
ListNode* fast=LongNode;
ListNode* slow=ShortNode;
while(gap)
{
fast=fast->next;
gap--;
}
while(fast!=slow)
{
fast=fast->next;
slow=slow->next;
}
return slow;
}
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
- 链表中节点的数目范围是
[0, 104]
-105 <= Node.val <= 105
pos
为-1
或者链表中的一个 有效索引。
解题代码与思路:
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head)
{
//快慢指针
ListNode* fast=head;
ListNode* slow=head;
//判断是否存在环
while(fast!=NULL&&fast->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
return true;
}
}
return false;
}
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
- 链表中节点的数目范围在范围
[0, 104]
内 -105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引
解题代码与思路:
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
ListNode* pcurA=headA;
ListNode* pcurB=headB;
int lenA=1;
int lenB=1;
while(pcurA!=NULL)
{
pcurA=pcurA->next;
lenA++;
}
while(pcurB!=NULL)
{
pcurB=pcurB->next;
lenB++;
}
int gap=abs(lenA-lenB);
ListNode* LongNode=headA;
ListNode* ShortNode=headB;
if(lenA<lenB)
{
LongNode=headB;
ShortNode=headA;
}
while(gap--)
{
LongNode=LongNode->next;
}
while(ShortNode!=LongNode)
{
LongNode=LongNode->next;
ShortNode=ShortNode->next;
}
return ShortNode;
}
struct ListNode *detectCycle(struct ListNode *head)
{
//先判断是否存在环
ListNode* slow=head;
ListNode* fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)//存在环
{
//寻找快慢指针相交节点
ListNode* meet=fast;
ListNode* newhead=fast->next;
fast->next=NULL;
ListNode* ptr=getIntersectionNode(head,newhead);
fast->next=newhead;
return ptr;
}
}
return NULL;
}
给你一个长度为 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
作为传入参数。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
提示:
0 <= n <= 1000
-104 <= Node.val <= 104
Node.random
为null
或指向链表中的节点。
解题代码与思路:
typedef struct Node Node;
//拷贝pcur节点
struct Node* CopyNodeMalloc(struct Node* pcur)
{
Node* copynode=(Node*)malloc(sizeof(Node));
copynode->val=pcur->val;
copynode->next=pcur->next;
copynode->random=pcur->random;
return copynode;
}
struct Node* copyRandomList(struct Node* head)
{
Node* pcur=head;
while(pcur!=NULL)
{
Node* copynode=CopyNodeMalloc(pcur);//拷贝节点
Node* pnext=pcur->next;//这里还是原链表pcur之后的那个节点
copynode->next=pnext;
pcur->next=copynode;
pcur=pnext;
}
//到这里已经拷贝并且插入完成
//控制random
pcur=head;
while(pcur!=NULL)
{
Node* copy=pcur->next;
if(pcur->random==NULL)
{
copy->random=NULL;
}
else{
copy->random=pcur->random->next;
}
pcur=copy->next;
}
//拷贝节点的random已经完成
//创建新链表
pcur=head;
Node* copyhead=NULL;
Node* copytail=NULL;
// Node* copy=pcur->next;
while(pcur!=NULL)
{
Node* copy=pcur->next;
Node* next=copy->next;
if(copytail==NULL)
{
copyhead=copytail=copy;
}else{
copytail->next=copy;
copytail=copytail->next;
}
pcur=next;
//copy=copy->next->next;
}
return copyhead;
}
null` 或指向链表中的节点。
解题代码与思路:
typedef struct Node Node;
//拷贝pcur节点
struct Node* CopyNodeMalloc(struct Node* pcur)
{
Node* copynode=(Node*)malloc(sizeof(Node));
copynode->val=pcur->val;
copynode->next=pcur->next;
copynode->random=pcur->random;
return copynode;
}
struct Node* copyRandomList(struct Node* head)
{
Node* pcur=head;
while(pcur!=NULL)
{
Node* copynode=CopyNodeMalloc(pcur);//拷贝节点
Node* pnext=pcur->next;//这里还是原链表pcur之后的那个节点
copynode->next=pnext;
pcur->next=copynode;
pcur=pnext;
}
//到这里已经拷贝并且插入完成
//控制random
pcur=head;
while(pcur!=NULL)
{
Node* copy=pcur->next;
if(pcur->random==NULL)
{
copy->random=NULL;
}
else{
copy->random=pcur->random->next;
}
pcur=copy->next;
}
//拷贝节点的random已经完成
//创建新链表
pcur=head;
Node* copyhead=NULL;
Node* copytail=NULL;
// Node* copy=pcur->next;
while(pcur!=NULL)
{
Node* copy=pcur->next;
Node* next=copy->next;
if(copytail==NULL)
{
copyhead=copytail=copy;
}else{
copytail->next=copy;
copytail=copytail->next;
}
pcur=next;
//copy=copy->next->next;
}
return copyhead;
}