初阶数据结构——单链表与双向链表及其应用

初阶数据结构——单链表与双向链表及其应用

  1. 链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次实现的。

链表的结构跟火车车厢相似,淡季时车厢会相应减少,旺季时车厢会额外增加。将火车中的的某节车厢去掉或加上,不会影响其他车厢,每节车厢都是独立存在的。

车厢是独立存在的,且每节车厢都有车门。假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下,我们想要从车头走向车位,最简单的情况就是:每节车厢里都放一把下一届车厢的钥匙。

对应到我们的链表中,与顺序表不同的是,链表中的每节"车厢"都是独立申请的空间,我们称之为“节点” ,节点的组成主要有两个部分:当前节点保存的数据和下一个节点的指针

注意:链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),所以我们需要通过指针变量来保存下一个节点位置,这样我们才能从当前节点找到下一个节点。

因此,我们可以给出每个节点对应的结构体代码:

struct SListNode
{
	int data;				//存储的数据
	struct SLTNode* pnext;	//下一个节点的地址
};
  1. 单链表的实现
  • 单链表接口实现:
#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);
  1. 单链表部分函数的实现
  • 单链表的尾插
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;
}
  1. 双向链表
  • 双向链表的概念:

注意:这里的“带头”跟单链表中我们说的“头节点”是两个概念,实际在单链表阶段称呼不严谨, 带头链表里的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里“放哨的” 。“哨兵位”存在的意义: 遍历循环链表避免死循环。

  • 对应节点的结构体代码
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;
}
  1. 链表经典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
  • l1l2 均按 非递减顺序 排列

解题代码与思路:

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;
}

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

自定义评测:

评测系统 的输入如下(你设计的程序 不适用 此输入):

  • intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
  • listA - 第一个链表
  • listB - 第二个链表
  • skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
  • skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数

评测系统将根据这些输入创建链式数据结构,并将两个头节点 headAheadB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案

示例 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:

img

输入: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
  • 如果 listAlistB 没有交点,intersectVal0
  • 如果 listAlistB 有交点,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 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点

例如,如果原链表中有 XY 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 xy ,同样有 x.random --> y

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0n-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.randomnull 或指向链表中的节点。

解题代码与思路:

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;
}
  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值