首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >题目练习之链表那些事儿(再续)

题目练习之链表那些事儿(再续)

作者头像
用户11352420
发布2024-11-07 21:39:33
发布2024-11-07 21:39:33
3140
举报
文章被收录于专栏:编程学习编程学习

这一篇博客我们继续在算法题的世界里面遨游~

链表分割

链表分割: https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70

这是一道来自牛客网上面的题目,我们需要将一个链表分割,同时不可以改变原来的顺序,这里提供一种思路~

思路

创建两个链表,一个 小链表保存比 x 小的结点,另外一个 大链表保存剩余的结点,再将 大链表和小链表首尾相连,得到我们想要的链表。(结合前面让代码更简单,我们可以向操作系统借空间创建链表,这样就不需要判断是否为空)

代码实现:

代码语言:javascript
复制
class Partition 
{
public:
    ListNode* partition(ListNode* pHead, int x)
    {
        //创建小链表
        ListNode* lessHead, * lessTail;
        //减少代码量,减少判断是否为空操作
        lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));

        //创建大链表
        ListNode* greaterHead, * greaterTail;
        greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));

        //遍历原来的链表
        ListNode* pcur = pHead;
        while (pcur)
        {
            //结点比x小放入小链表(尾插)
            if (pcur -> val < x)
            {
                lessTail->next = pcur;
                lessTail = pcur;
            }
            //结点比x大放入大链表(尾插)
            else
            {
                greaterTail->next = pcur;
                greaterTail = pcur;
            }
            //继续遍历
            pcur = pcur->next;
        }
        //首尾相连
        lessTail->next = greaterHead->next;
        //大链表尾结点下一个结点置为空,避免死循环
        greaterTail->next = NULL;
        //保存返回的结点
        ListNode* reNode = lessHead->next;
        //释放动态申请的空间(一块空间释放一次就可以了)
        free(lessHead);
        lessHead = NULL;
        free(greaterHead);
        greaterHead = NULL;

        //返回结点
        return reNode;
    }
}

我们的代码运行成功~

相交链表

相交链表: https://leetcode.cn/problems/intersection-of-two-linked-lists/description/

两个单链表相交是什么情况呢?

思路

我们知道单链表一个结点包括保存的值和指向下一个结点的指针,如果两个单链表相交,那么我们可以肯定它们尾结点的地址一定是相同的,那么这里需要返回相交结点的地址,我们可以怎么做呢?思路如下:

因为两个链表的结点个数不一定是一样的,所以我们可以分别统计两个单链表的结点个数,成为长链表和短链表,求出结点个数差值gap,让长链表先走gap步,这样长链表和短链表就在同一起跑线上,两个单链表同时开始遍历,判断结点是否相同,相同就返回当前结点,否则返回NULL。

代码实现:

代码语言:javascript
复制
typedef struct ListNode ListNode;
struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB) 
{
	//特殊处理,如果有一个为空直接返回NULL
	//分开处理
	if (headA == NULL)
	{
		return NULL;
	}
	if (headB == NULL)
	{
		return NULL;
	}
  //遍历两个链表统计结点个数
	ListNode* pcurA = headA;
	ListNode* pcurB = headB;

	int sizeA = 0;
	int sizeB = 0;
	while (pcurA)
	{
		sizeA++;
		pcurA = pcurA->next;
	}
	while (pcurB)
	{
		sizeB++;
		pcurB = pcurB->next;
	}

	//求结点差gap
	int gap = abs(sizeA - sizeB);
	//abs求绝对值,头文件math.h
	
	//假设长链表和短链表
	ListNode* LongList = headA;
	ListNode* ShortList = headB;
	//另外一种情况
	if (sizeA < sizeB)
	{
		LongList = headB;
		ShortList = headA;
	}
	//长链表先走gap步
	while (gap--)
	{
		LongList = LongList->next;
	}
	//长链表和短链表在同一起跑线,剩下的结点个数相同
	//遍历两个链表比较
	while (LongList)//或者while (ShortList)  //剩下的结点数相同都可以
	{
		if (LongList == ShortList)
		{
			return LongList;
			//或者return ShortList
		}
		LongList = LongList->next; 
		ShortList = ShortList->next;
	}
	//结束也没有相同结点,返回NULL
	return NULL;
}

代码成功通过,当然这里其实也不用前面的特殊处理,因为题目给出了m,n的个数是大于等于1的,那就不会有空链表的情况,写上当然可以让我们代码更加完善~

环形链表I

环形链表I : https://leetcode.cn/problems/linked-list-cycle/description/

来看看题目链表带环返回true,不带环返回false,这里与我们的双向链表不一样,它不一定是从头结点开始就是循环的,而是从链表中一个结点往后面开始循环,这里我们使用什么方法判断呢?

答案是快慢指针法

思路

原理: 快慢指针,慢指针每次走⼀步,快指针每次走两步,两个指针从链表起始位置开始运⾏, 如果 链表带环则⼀定会在环中相遇 , 否则快指针率先走到链表的未尾 。

有人可能会问为什么呢?我们来看看

解析: 类似于在跑道上进行跑步游戏,一个人跑得慢,一个人跑得快,从同一个起点出发,如果只跑到终点,那么跑得快的人先到终点,如果一直跑圈,那么两个人之间的距离会先增加再减少,最后为0,两人也就相遇了。 跑得快的就是我们这里的快指针,跑得慢的就是我们这里的慢指针,如果链表不带环,那么终点就是NULL,如果链表带环,那么快慢指针一定会相遇。通过生活中的例子是不是更好地理解呢? 接下来,通过我们的代码实现

代码语言:javascript
复制
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode* head) {

    //使用快慢指针
    ListNode* slow = head;
    ListNode* fast = head;
    //快指针每次走两步
    //慢指针每次走一步
    while (fast && fast->next)
    {
        //先往后面走再比较,因为最开始slow和fast都是指向head
        slow = slow->next;
        fast = fast->next->next;
        if (fast == slow)
        {
            //快慢指针相遇会带环
            return true;
        }
       
    }
    //走到NULL,不带环
    return false;
}

代码提交通过,这也是一种十分巧妙的方法,当然这里快指针可以走两步,也可以走三步或者其他的步数,就按照我们跑步的理解都是可以的,但是我们平时使用快慢指针一般习惯于快指针走两步,慢指针走一步。接下来我们来看看进阶版的环形链表~

环形链表II

环形链表II: https://leetcode.cn/problems/linked-list-cycle-ii/description/

这里不仅仅是判断链表是否带环,并且还要环开始的结点,这又应该怎么做呢?

思路

首先给出结论:

1.让⼀个指针 从链表起始位置开始遍历链表 , 同时让 ⼀个指针从判环时相遇点的位置开始绕环运行 2.两个指针都是 每次均走⼀步, 3.最终 肯定会在入口点的位置相遇 。

有人可能就会怀疑这是真的吗?接下来我们来证明一下

将环进行形象化表示(环逆时针走) H为链表的起始点,E为环入口点,M与判环时候快慢指针相遇点 设: 环的长度为R,H到E的距离为L,E到M的距离为 X 则: M到E的距离为 R-X 在判环时,快慢指针相遇时所走的路径长度: fast: L+X + nR (慢指针入环,快指针可能已经走了n圈,n>=1) slow : L+X 根据 2*慢指针=快指针 可以得出 2 *(L+X)=L+X+nR L+X=nR L=nR-X L=(n-1)R+R-X (数学处理方式) n的大小取决于环的大小,环越小n越⼤ 极端情况下,假设n=1,此时: L=R-X 这里L的大小就等于相遇点到入环点的距离大小,最终都会到入环点,所以我们得出结论: ⼀个指针从链表起始位置运行,⼀个指针从相遇点位置绕环,每次都走⼀步,两个指针最终会在入口点的位置相遇

代码实现:

代码语言:javascript
复制
typedef struct ListNode ListNode;
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* pcur = head;
			//一个指针从头结点开始走
			//一个指针从相遇点开始走
			//两个指针一定会相遇
			while (pcur != slow)
			{
				pcur = pcur->next;
				slow = slow->next;
			}
			//相遇点就是入口点
			return pcur;
            //或者return slow;
		}
	}
	//不带环返回NULL
	return NULL;
}

代码提交通过~这道题的思路不好想,但是一个算法题我们认真分析,也是可以用高效的方法解决的~~

随机链表的复制

随机链表的复制: https://leetcode.cn/problems/copy-list-with-random-pointer/description/

看着这题目这么长一串,你是不是就感觉大事不妙?别急~我们来好好看一下这个题目~

这里与我们的双向链表有点类似,但是却又不完全相同,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点,当然还有一个指针就是next了。

我们这一个应该怎么办呢?

这里提供一个巧妙的思路~

思路

1.在原来的链表上拷贝每一个结点到当前结点的后面 2.处理random指针 3.断开新旧链表

代码语言:javascript
复制
typedef struct Node Node;
Node* buyNode(int x)
{
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL)
    {
        perror("malloc fail\n");
    }
    else
    {
        newNode->val = x;
        newNode->next = newNode->random = NULL;
    }
    return newNode;
}
void addNode(Node* head)
{
    //head不会为空
    Node* pcur = head;
    //遍历接结点
    while (pcur)
    {
        //保存原来链表当前结点的下一个结点
        Node* Next = pcur->next;
        //拷贝结点
        Node* addnode = buyNode(pcur->val);
        pcur->next = addnode;
        addnode->next = Next;
        pcur = Next;
    }
}
struct Node* copyRandomList(struct Node* head)
{
    //特殊处理链表为空
    if (head == NULL)
    {
        return NULL;
    }
    //拷贝每一个结点到当前结点后面
    addNode(head);
    //处理random指针
    Node* pcur = head;
    while (pcur)
    {
        //找到拷贝的结点
        Node* copy = pcur->next;
        if (pcur -> random != NULL)
        {
            //random为原来链表random的下一个指向
            copy->random = pcur->random->next;
        }
        //往后面遍历处理
        pcur = copy->next;
    }
    //断开新旧链表
    pcur = head;
    //pcur重新指向头结点
    Node* newTail, * newHead;
    //从拷贝的第一个结点开始
    newTail = newHead = pcur->next;
    while (newTail->next)
    {
        pcur = newTail->next;
        newTail->next = pcur->next;
        newTail = newTail->next;
    }
    return newHead;
}

代码提交通过~里面很多的知识点还需要自己去慢慢领悟~~~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-30,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 链表分割
    • 思路
  • 相交链表
    • 思路
  • 环形链表I
    • 思路
  • 环形链表II
    • 思路
  • 随机链表的复制
    • 思路
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档