链表OJ题目

题目难度依次递增

移除链表元素

题目链接:LeetCode203.移除链表元素

题目
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点
在这里插入图片描述
提示:

  • 列表中的节点数目在范围 [0, 104] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50
    在这里插入图片描述
    题解: 方法一
struct ListNode* removeElements(struct ListNode* head, int val)
{
	struct ListNode* prev = NULL;
	struct ListNode* cur = head;//把头指针赋给cur
	while (cur)//查找链表结束条件
	{
		if (cur->val != val)
		{
			prev = cur;//标记cur的前一个指针
			cur = cur->next;//让cur指向下一个结点
		}
		else
		{
			struct ListNode* next = cur->next;//定义cur结点的下一个位置
			if (prev == NULL)//头结点就是val的情况
			{
				free(cur);//释放当前结点
				head = next;//让头指针指向下一个结点
				cur = next;//让cur指向下一个结点
			}
			else
			{
				free(cur);//释放cur结点
				prev->next = next;//让prev指向cur的下一个结点
				cur = next;//让cur指向下一个结点
			}
		}
	}
	return head;//返回头指针地址
}

方法二:

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode*cur=head;
    struct ListNode*prev=NULL;
    while(cur)
    {
        if(cur->val!=val)
        {
           prev=cur;
           cur=cur->next;
        }
        else
        {
            if(prev==NULL)
            {
                head=cur->next;
                free(cur);
                cur=head;
            }
            else
            {
                prev->next=cur->next;
                free(cur);
                cur=prev->next;
            }
        }
    }
    return head;
}

反转链表

题目链接: LeetCode206.反转链表

题目:
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
在这里插入图片描述
在这里插入图片描述
题目分析:
在这里插入图片描述
题解: 方法一

struct ListNode* reverseList(struct ListNode* head)
{
	if (head == NULL)//链表为空的情况
		return NULL;
	struct ListNode* n1, * n2, * n3;
	n1 = NULL;
	n2 = head;
	n3 = n2->next;//n3指向n2的下一个结点
	while (n2)
	{
		n2->next = n1;//让n2这个结点指向n1
		n1 = n2;
		n2 = n3;
		if (n3)//如果n3为空,则程序会崩溃,所以判断一下
			n3 = n3->next;
	}
	return n1;//返回反转链表后的头结点
}

方法二:
在这里插入图片描述

struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* newhead = NULL;
    struct ListNode* cur = head;
    while (cur)//cur不为NULL时执行循环
    {
        struct ListNode* next = cur->next;
        //头插
        cur->next = newhead;//让cur指向新的头结点
        newhead = cur;//更新newhead
        cur = next;//让cur继续向后走
    }
    return newhead;//返回新的头结点
}

链表的中间结点

题目链接: LeetCode876.链表的中间结点
题目:
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
在这里插入图片描述
提示: 给定链表的结点数介于 1 和 100 之间。

题目分析:
我们定义两个指针,slow和fast。slow每次走一步,fast每次走两步。
在这里插入图片描述
题解:

struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode*slow,*fast;
    slow=fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}

思考: 上诉代码中 while(fast&&fast->next) 可不可以写为:while(fast->next&&fast)?

注意: 我们是不可以将while中的条件调换的,如果我们将while中的条件调换,那么当链表中结点为偶数的时候,结束时fast已经为空了,我们不能用空指针来访问next,这样做代码就会崩溃。我们应该采用第一种写法(while(fast&&fast->next)),当fast为空时便不会在判断后面的条件,而是直接跳出循环。

链表中倒数第K个结点

题目链接: 牛客网:链表中倒数第K个结点

题目:
输入一个链表,输出该链表中倒数第k个结点。
在这里插入图片描述
题目分析:
我们先定义两个指针,fast和slow。
1.fast先走K步。
2.slow和fast同时走,fast走到尾时,slow就是倒数第K个。

在这里插入图片描述

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k)
{
    struct ListNode* slow, * fast;
    slow = fast = pListHead;
    //fast先走K步
    while (k--)
    {
        if (fast == NULL)//如果链表为空了,就提前结束
            return NULL;
        fast = fast->next;
    }
    while (fast)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;//返回倒数第K个结点的地址
}

合并两个有序链表

题目链接: LeetCode21.合并两个有序链表

题目:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
在这里插入图片描述
在这里插入图片描述
提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • 11 和 12 均按 非递减顺序 排列

题目分析: 我们依次取出两个链表中较小的去尾插,当一个链表为空时,将另一个链表链接在目标链表的尾部。
在这里插入图片描述
题解: 方法一:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    //若有链表为空的情况
    if (list1 == NULL)
        return list2;
    if (list2 == NULL)
        return list1;

    struct ListNode* tail, * head;
    tail = head = NULL;
    while (list1 && list2)//如果list1和list2都不为空就进入循环
    {
        if (list1->val < list2->val)
        {
            if (head == NULL)//头结点为空的情况
            {
                tail = head = list1;
            }
            else
            {
                tail->next = list1;
                tail = list1;
            }
            list1 = list1->next;
        }
        else
        {
            if (head == NULL)
            {
                tail = head = list2;
            }
            else
            {
                tail->next = list2;
                tail = list2;
            }
            list2 = list2->next;
        }
    }

    //循环结束后,将另外一个没有走完的链表直接链接在目标链表的后面
    if (list1 == NULL)
        tail->next = list2;
    if (list2 == NULL)
        tail->next = list1;

    return head;//返回目标链表头结点的地址
}

方法二:定义一个带哨兵位的头结点

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* head = NULL, * tail = NULL;
    //带哨兵位的
    head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));//定义一个哨兵位头结点
    head->next = NULL;//让哨兵位指向空
    while (list1 && list2)
    {
        if (list1->val < list2->val)
        {
            tail->next = list1;
            tail = list1;
            list1 = list1->next;
        }
        else
        {
            tail->next = list2;
            tail = list2;
            list2 = list2->next;
        }
    }
    if (list1 == NULL)
        tail->next = list2;
    if (list2 == NULL)
        tail->next = list1;

    struct ListNode* list = head->next;
    free(head);//释放申请的哨兵位头结点
    return list;
}

链表分割

题目链接: CM11 链表分割

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

题目分析:
遍历原链表。把 <X 的插入到链表1,把 >X 的插入到链表2。 链表1和链表2链接起来。
在这里插入图片描述
题解:

class Partition {
public:
    ListNode* partition(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));
    LessHead->next = GreaterHead->next = NULL;//让哨兵位的头结点指向空

    struct ListNode* cur = pHead;
    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;//将GreaterHead链接到LessTail上面
    GreaterTail->next = NULL;//让目标链表的尾结点指向空,避免最后一个结点存着链表中某一个结点的地址,从而指向那个结点造成链表的部分循环。
    struct ListNode* list = LessHead->next;//定义list指向目标链表的头结点
    free(LessHead);//释放申请的哨兵位结点
    free(GreaterHead);//释放申请的哨兵位结点
    return list;//返回目标链表的头结点
    }
};

链表的回文结构

题目链接: OR36 链表的回文结构

题目:
对于一个链表,请设计一个时间复杂度为O(n),额外**空间复杂度为O(1)**的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

测试样例:1->2->2->1
返回:true

题目分析:
在这里插入图片描述
题解:

class PalindromeList {
public:
//反转链表,然会返回目标链表的头结点
struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* newhead = NULL;
    struct ListNode* cur = head;
    while (cur)//cur不为NULL时执行循环
    {
        struct ListNode* next = cur->next;
        //头插
        cur->next = newhead;//让cur指向新的头结点
        newhead = cur;//更新newhead
        cur = next;//让cur继续向后走
    }
    return newhead;//返回新的头结点
}

//找到链表的中间结点并返回地址
struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* slow, * fast;
    slow = fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

bool chkPalindrome(struct ListNode* A)
{
    struct ListNode* mid = middleNode(A);//找到A链表的中间结点
    struct ListNode* rHead = reverseList(mid);//反转mid之后的链表
    while (A && rHead)//其中一个链表走完循环就结束
    {
        if (A->val == rHead->val)
        {
            A = A->next;
            rHead = rHead->next;
        }
        else
            return false;
    }
    return true;
}
};

链表相交

题目链接: LeetCode160.相交链表

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

图示两个链表在节点 c1 开始相交:
在这里插入图片描述
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构
在这里插入图片描述
题目分析:
在这里插入图片描述

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB) 
{
    struct ListNode* tailA = headA, * tailB = headB;
    int lenA = 1, lenB = 1;//让最开始的lenA和lenB的值为1,否则计算的链表长度会比实际长度小1
    while (tailA->next)
    {
        tailA = tailA->next;
        ++lenA;//计算A链表的长度
    }
    while (tailB->next)
    {
        tailB = tailB->next;
        ++lenB;//计算B链表的长度
    }
    if (tailA != tailB)//如果两个链表的尾结点不相等,那么链表就不相交
        return NULL;

    //链表相交,求交点。长的链表先走差距步,再同时走找交点。
    struct ListNode* shortList = headA, * longList = headB;//定义的时候默认A链表为短链表
    if (lenA > lenB)//如果A链表比较长,就小调整一下
    {
        longList = headA;
        shortList = headB;
    }

    int gap = abs(lenA - lenB);//链表A与链表B的长度差
    while (gap--)//让较长的链表先走差距步
    {
        longList = longList->next;
    }
    while (shortList && longList)//A链表和B链表都不为空,则进入循环
    {
        if (shortList == longList)
            return shortList;//返回两个链表的交点
        shortList = shortList->next;
        longList = longList->next;
    }
    return NULL;//编译器检查语法的时候,必须在这里有返回值,否则就会出现语法错误。尽管代码的逻辑执行并没有错。
}

注意:
这里我们需要特别注意一点,在代码末尾一行的时候,我们必须写上返回值,即使这个代码不会执行到这一行。因为编译器对代码进行语法检查的时候,它并不会去看你执行代码的逻辑是否正确,而是看你的语法是否有误,如果不写这一行,编译器就会认为没有返回值而报出语法错误。

环形链表

题目链接:LeetCode141.环形链表

题目:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
在这里插入图片描述
题目分析:
思路:快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。

问:在带环链表中,为什么快指针每次走两步,慢指针走一步就一定会追上?

证明如下:
在这里插入图片描述
问题扩展:
slow一次走1步,fast一次走3步,能追上吗?fast一次走4步呢?n步呢?
在这里插入图片描述
结论:因为链表的长度是不一定的,所以在带环链表中,若想要slow(慢指针)能够追上fast(快指针),只能够让slow每次走一步,fast每次走两步,这样就一定会追上。如果slow每次走一步,fast走两步以上,那么将不一定会追上。

题解:

bool hasCycle(struct ListNode* head)
{
    struct ListNode* slow, * fast;
    slow = fast = head;//slow和fast同时指向头结点
    while (fast && fast->next)//链表结点可能为奇数也可能为偶数,所以需要这样判断。但判断的条件顺序不能够颠倒。
    {
        fast = fast->next->next;//fast向后走两步
        slow = slow->next;//slow向后走一步
        if (slow == fast)//如果相遇
            return true;
    }
    return false;
}

环形链表II

题目链接:LeetCode142.环形链表II

题目:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改链表
在这里插入图片描述
题目分析:
结论:让一个指针从链表起始位置开始遍历链表,同时让一个指针从偶判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。

证明:
在这里插入图片描述
题解:

struct ListNode* detectCycle(struct ListNode* head)
{
    struct ListNode* slow, * fast;
    slow = fast = head;//slow和fast同时指向头结点
    while (fast && fast->next)//链表结点可能为奇数也可能为偶数,所以需要这样判断。但判断的条件顺序不能够颠倒。
    {
        fast = fast->next->next;//fast向后走两步
        slow = slow->next;//slow向后走一步
        if (slow == fast)//如果相遇
        {
            struct ListNode* meet = slow;
            while (meet != head)
            {
                meet = meet->next;
                head = head->next;
            }
            return meet;
        }
    }
    return NULL;
}

方法二:
如果我们想不到上面那个方法,那么这题我们也可以转换成链表相交问题。可以参考上面的相交链表的代码。
在这里插入图片描述

struct ListNode* detectCycle(struct ListNode* head)
{
    int lenA = 1, lenB = 1;//让最开始的lenA和lenB的值为1,否则计算的链表长度会比实际长度小
    struct ListNode* slow, * fast;
    slow = fast = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            struct ListNode* meet = slow, * headB, * headA = head;
            headB = meet->next;
            meet->next = NULL;

            struct ListNode* tailA = head, * tailB = headB;
            while (tailA->next)
            {
                tailA = tailA->next;
                ++lenA;//计算A链表的长度
            }
            while (tailB->next)
            {
                tailB = tailB->next;
                ++lenB;//计算B链表的长度
            }
            if (tailA != tailB)//如果两个链表的尾结点不相等,那么链表就不相交
                return NULL;

            //链表相交,求交点。长的链表先走差距步,再同时走找交点。
            struct ListNode* shortList = headA, * longList = headB;//定义的时候默认A链表为短链表
            if (lenA > lenB)//如果A链表比较长,就小调整一下
            {
                longList = headA;
                shortList = headB;
            }

            int gap = abs(lenA - lenB);//链表A与链表B的长度差
            while (gap--)//让较长的链表先走差距步
            {
                longList = longList->next;
            }
            while (shortList && longList)//A链表和B链表都不为空,则进入循环
            {
                if (shortList == longList)
                    return shortList;//返回两个链表的交点
                shortList = shortList->next;
                longList = longList->next;
            }
        }
    }
    return NULL;
}

复制带随机指针的链表

题目链接:LeetCode138.复制带随机指针的链表

题目: 给你一个长度为 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 作为传入参数。
在这里插入图片描述
题目分析:
在这里插入图片描述
题解:

struct Node* copyRandomList(struct Node* head)
{
    //1.拷贝结点连接在原结点的后面
    struct Node* cur = head;
    while (cur)
    {
        struct Node* copy = (struct Node*)malloc(sizeof(struct Node));//申请一个结点
        copy->val = cur->val;//拷贝原结点的值到拷贝结点中
        copy->next = cur->next;//让copy结点指向原结点的下一个结点
        cur->next = copy;//原结点指向copy结点
        cur = cur->next->next;
    }

    //2.更新拷贝结点的random
    cur = head;
    while (cur)
    {
        struct Node* copy = cur->next;
        if (cur->random == NULL)//random为空的情况
        {
            copy->random = NULL;
        }
        else
        {
            copy->random = cur->random->next;//拷贝结点的random指向原结点的random的next
        }
        cur = cur->next->next;
    }

    //3.将拷贝结点解下来,连接到一起
    struct Node* copyHead, * copyTail;
    copyHead = copyTail = NULL;//定义一个新链表的头和尾
    cur = head;
    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;//返回拷贝链表的头结点
}

在这里插入图片描述

  • 30
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 26
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风&57

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值