[算法] leetcode单链表相关题目详解(三)

1. leetcode_141-环形链表

给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置
(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意: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 或者链表中的一个 有效索引 。

https://leetcode-cn.com/problems/linked-list-cycle/description/

解法

  判断链表是否有环,可以使用快慢指针的方式进行判断,快指针每次走两步,慢指针每次走一步。
  如果链表中有环,则在环内两指针一定会相遇。

bool hasCycle(struct ListNode* head) 
{
	if (NULL == head || NULL == head->next)
		return false;

	struct ListNode* slow = head;
	struct ListNode* fast = head;

	while (fast != NULL && fast->next != NULL)
	{
		slow = slow->next;
		fast = fast->next->next;

		if (slow == fast)
			return true;
	}

	return false;
}

对于快慢指针的说明

  通常快指针都是每次移动两步,慢指针每次移动一步,为什么快指针一定要两步两步进行移动。
在这里插入图片描述
  观察这个图片,可以看出这是一个循环链表,但fast每次移动3步,slow每次移动一步,他们永远都不会相遇。

2. leetcode_142-环形链表 II

	142. 环形链表 II
	
	给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
	为了表示给定链表中的环,我们使用整数 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 或者链表中的一个有效索引

	https://leetcode-cn.com/problems/linked-list-cycle-ii/description/

解法

  该题是求环形链表的入环点。根据上题可以得出,如果链表有环,则一定会在环内某一点相交,所以可以引出下列数学问题:
在这里插入图片描述

设链表头为H,入口点为E,相遇点M
 H->E的距离为l,E->M的距离为x,环的大小为r

根据以上定义可以推导出:
  当两个节点相遇时两指针走过的距离: fast = l + x + nr , slow = l + x
  因为fast指针每次走的步数时slow的两倍。所以有: 2 * slow = fast; 所以: 2*(l+x) = l+x+nr -> l = nr - x;
  根据上述结论,可以得到,入口点的位置就为从链表头开始,走 L 步的位置就为入口点,但是这个点需要通过两个变量来确定。
所以定义两个指针,一个指向链表头,一个指向相遇点,两个指针一起走,当相遇时,就为环形链表的入环点。

代码为:

struct ListNode* hasCycle(struct ListNode* head)
{
	if (NULL == head || NULL == head->next)
		return NULL;

	struct ListNode* slow = head;
	struct ListNode* fast = head;

	while (fast != NULL && fast->next != NULL)
	{
		slow = slow->next;
		fast = fast->next->next;

		if (slow == fast)
			return slow;
	}

	return NULL;
}

struct ListNode* detectCycle(struct ListNode* head) 
{
	if (NULL == head || NULL == head->next)
		return NULL;

	struct ListNode* pTmp = head;

	// 判断链表是否有环
	struct ListNode* pRes = hasCycle(head);
	if (pRes == NULL)
		return NULL;
	else
	{
	// 两链表同时移动,直到节点地址相同
		while (pTmp != pRes)
		{
			pTmp = pTmp->next;
			pRes = pRes->next;
		}
	}

	return pTmp;
}

3. leetcode_138-复制带随机指针的链表

给你一个长度为 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]]

示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:
0 <= n <= 1000
-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。

https://leetcode-cn.com/problems/copy-list-with-random-pointer/description/

思路分析

在这里插入图片描述

该题有一个随机指针的指向需要拷贝,所以分为以下3步完成
1.在原链表每个节点之后都插入值相等的新节点
在这里插入图片描述

  2.将新加入节点的随机指针域进行赋值,观察上图可知新节点的随机指针域恰好为原节点随机指针域的下一个元素所以就有newNode->random = pCur->random->next ,pCur为新加入节点的上一个元素
  这里要注意random指针指向NULL的情况
在这里插入图片描述
4.将新加入的节点拆分出来
  构成拷贝完成后的链表。

代码

struct Node* copyRandomList(struct Node* head) 
{
	if (NULL == head)
		return NULL;

	struct Node* pCur = head;
	struct Node* pNew = NULL;

	// 1. 在原链表的每个节点之后插入值相等的新节点
	while (NULL != pCur)
	{
		pNew = (struct Node*)malloc(sizeof(struct Node));
		pNew->next = NULL;
		pNew->random = NULL;
		pNew->val = pCur->val;

		// 将新节点放入原链表中
		pNew->next = pCur->next;
		pCur->next = pNew;

		pCur = pNew->next;
	}

	// 2.对新节点中的random域进行赋值 pCur指向原链表中的元素,pNew指向新链表中的元素
	// 所以pNew-random = pCur->random->next;pCur中的random指针域指向的为原链表中的元素,next指向值与他相同的新节点
	pCur = head;
	pNew = pCur->next;

	while (NULL != pCur && NULL != pNew)
	{
		// 如果pCur的random为空 则新节点也指向NULL
		if (NULL == pCur->random)
			pNew->random = NULL;
		else
			pNew->random = pCur->random->next;

		if (NULL == pNew->next)
			break;
		pCur = pNew->next;
		pNew = pCur->next;
	}

	// 3.将新申请节点进行拆除,并恢复链表
	pCur = head;
	pNew = head->next;
	struct Node* pRes = pNew;

	while (NULL != pCur && NULL != pCur->next && NULL != pNew->next)
	{
		pCur->next = pNew->next;
		pCur = pNew->next;

		pNew->next = pCur->next;
		pNew = pCur->next;
	}

	return pRes;
}

4. leetcode_147-对链表进行插入排序

对链表进行插入排序。
插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。

插入排序算法:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。

示例 1:
输入: 4->2->1->3
输出: 1->2->3->4

示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5

https://leetcode-cn.com/problems/insertion-sort-list/

解法

  因为这是一个插入排序,每次都要取出待排数据,查找插入位置。所以构造新链表,将原链表中的数据逐个拆出,按照次序放入新链表中。

struct ListNode* insertionSortList(struct ListNode* head) 
{
	if (NULL == head || NULL == head->next)
		return head;

	struct ListNode* pNewL = (struct ListNode*)malloc(sizeof(struct ListNode));
	pNewL->val = -1;
	pNewL->next = NULL;
	struct ListNode* pRes = pNewL;
	
	while (head != NULL)
	{
		// 查找待插入位置
		while (NULL != pNewL->next && head->val > pNewL->next->val)
		{
			pNewL = pNewL->next;
		}

		struct ListNode* pIns = head;
		// 先对头指针指向进行改变,否则会插入节点的操作
		head = head->next;

		// 插入节点
		pIns->next = pNewL->next;
		pNewL->next = pIns;
		pNewL = pRes;		// 一次插入排序完成后,回到起点重新查找位置
	}

	return pRes->next;
}

5. leetcode_83-删除排序链表中的重复元素

存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。

返回同样按升序排列的结果链表。

示例 1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

示例 2:
输入:head = [1,1,1,2,3]
输出:[2,3]

提示:
链表中节点数目在范围 [0, 300]-100 <= Node.val <= 100
题目数据保证链表已经按升序排列

https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/

解法

  这个题目中,要求去除所有重复的数据,只显示不重复的数据。
  对出现值进行判断,如果重复,则跳过所有关于这个值的节点,如果不重复则将该节点加入到新链表中。

// 因为链表有序,所以判断后方元素是否等于当前元素,如果相等则全部跳过
// 将不相等的元素尾插进新链表中
struct ListNode* deleteDuplicates(struct ListNode* head) 
{
	if (NULL == head || NULL == head->next)
		return head;

	
	struct ListNode* pPre = (struct ListNode*)malloc(sizeof(struct ListNode));
	pPre->next = NULL;
	struct ListNode* pRes = pPre;

	while (NULL != head)
	{
		int value = head->val;

		// 跳过所有相等值的节点
		if (NULL != head->next && head->next->val == value)
		{
			head = head->next;
			while (NULL != head && head->val == value)
			{
				head = head->next;
			}
		}
		else
		{
			// 把不重复的节点值加入到新链表中
			pPre->next = head;
			pPre = pPre->next;
			head = head->next;
			pPre->next = NULL;
		}
		
	}

	return pRes->next;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值