链表相关题目详解

链表相关特点:无法高效获取长度,无法根据偏移快速访问元素。
根据链表的特点衍生出一系列问题:判断环的长度等和长度与位置有关的问题,获取倒数第k个元素,获取中间位置的元素,判断链表是否存在环。这些问题都可以通过灵活运用双指针来解决。

一、判断链表是否有环
141. 环形链表
思路:现在考虑一个环形链表,把慢指针和快指针想象成两个在环形赛道上跑步的运动员(分别称之为慢跑者与快跑者)。而快跑者最终一定会追上慢跑者。这是为什么呢?考虑下面这种情况(记作情况 A)- 假如快跑者只落后慢跑者一步,在下一次迭代中,它们就会分别跑了一步或两步并相遇。

/**
 * 已经定义的 ListNode 结点
 * typedef struct Node {
 *     int val;
 *     struct Node *next;
 * } ListNode;
 * #define bool int
 * #define true 1
 * #define false 0
 */
bool isLinkedListCycle(ListNode *head) {
    ListNode * p  = head,* q = head; //q是快指针,p是慢指针
    while(q != NULL){
        q = q->next;
        if(q != NULL){
            q = q->next;
        }else{
            return false;
        }
        p = p->next;
        if(p == q){
         return true;   
        }
    }
    return false;
}

二、求链表环的链接点
142. 环形链表 II
解法:Floyd 算法
算法
Floyd 的算法被划分成两个不同的 阶段 。在第一阶段,找出列表中是否有环,如果没有环,可以直接返回 null 并退出。否则,用 相遇节点 来找到环的入口。

阶段 1

这里我们初始化两个指针 - 快指针和慢指针。我们每次移动慢指针一步、快指针两步,直到快指针无法继续往前移动。如果在某次移动后,快慢指针指向了同一个节点,我们就返回它。否则,我们继续,直到 while 循环终止且没有返回任何节点,这种情况说明没有成环,我们返回 null 。

下图说明了这个算法的工作方式:
在这里插入图片描述
环中的节点从 0 到 C-1 编号,其中 C 是环的长度。非环节点从 -F到 -1编号,其中 F是环以外节点的数目。 F 次迭代以后,慢指针指向了 0 且快指针指向某个节点 h ,其中 F≡h(modC) 。这是因为快指针在 F次迭代中遍历了 2F个节点,且恰好有 F 个在环中。继续迭代 C−h 次,慢指针显然指向第 C-h 号节点,而快指针也会指向相同的节点。原因在于,快指针从 h号节点出发遍历了 2(C-h)个节点。

在这里插入图片描述

因此,如果列表是有环的,快指针和慢指针最后会同时指向同一个节点,因此被称为 相遇 。
阶段 2

给定阶段 1 找到的相遇点,阶段 2 将找到环的入口。首先我们初始化额外的两个指针: ptr1 ,指向链表的头, ptr2 指向相遇点。然后,我们每次将它们往前移动一步,直到它们相遇,它们相遇的点就是环的入口,返回这个节点。
在这里插入图片描述
我们利用已知的条件:慢指针移动 1 步,快指针移动 2 步,来说明它们相遇在环的入口处。(下面证明中的 tortoise 表示慢指针,hare 表示快指针)
在这里插入图片描述
因为 F=b ,指针从 h 点出发和从链表的头出发,最后会遍历相同数目的节点后在环的入口处相遇。

/**
 * 已经定义的 ListNode 结点
 * typedef struct Node {
 *     int val;
 *     struct Node *next;
 * } ListNode;
 */
ListNode* linkedListCycleLinkedNode(ListNode *head) {
    ListNode * p = head ,*q = head;
    ListNode *ans =  NULL;
    while(q){
        q = q->next;
        if(q != NULL){
            q = q->next;
        }else{
            return ans;
        }
        p = p->next;
        if(p == q){
            break;
        }
    }
  if(q == NULL)  return ans;
    p = head;
    while(p != q){
        p = p->next;
        q = q->next;
    }
    return p;
}
class Solution {
public:
    ListNode* detectCycle(ListNode* head) {
	ListNode* fastPtr=head, *slowPtr=head;
	while (fastPtr!=NULL && fastPtr->next!=NULL)
	{
		fastPtr = fastPtr->next->next;
		slowPtr = slowPtr->next;
		if (fastPtr==slowPtr)
		{
			fastPtr = head;
			while (fastPtr != slowPtr)
			{
				fastPtr = fastPtr->next;
				slowPtr = slowPtr->next;
			}
			return fastPtr;
			break;
		}
	}

	return nullptr;
}

三、求环的长度
思路: 在环上相遇后,记录第一次相遇点为Pos,之后指针slow继续每次走1步,fast每次走2步。在下次相遇的时候fast比slow正好又多走了一圈,也就是多走的距离等于环长。
设从第一次相遇到第二次相遇,设slow走了len步,则fast走了2len步,相遇时多走了一圈:环长=2len-len。

/**
 * 已经定义的 ListNode 结点
 * typedef struct Node {
 *     int val;
 *     struct Node *next;
 * } ListNode;
 */
int linkedListCycleLength(ListNode *head) {
    ListNode * p = head, *q = head;
    int ans = 1;
    while(q && q->next){
        q = q->next->next;
        p = p->next;
        if(p == q){
            q = q->next->next;
            p = p->next;
            while(q != p){
                q = q->next->next;
                p = p->next;
                ans++;
            }
            return ans;
        }
    }
    return NULL;
}

四、倒数第K个节点
思路:“倒数第k个元素的问题”。设有两个指针 p 和 q,初始时均指向头结点。首先,先让 p 沿着 next 移动 k 次。此时,p 指向第 k+1个结点,q 指向头节点,两个指针的距离为 k 。然后,同时移动 p 和 q,直到 p 指向空,此时 p 即指向倒数第 k 个结点。
五、获取中间位置的元素
思路:获取中间元素的问题。设有两个指针 fast 和 slow,初始时指向头节点。每次移动时,fast向后走两次,slow向后走一次,直到 fast 无法向后走两次。这使得在每轮移动之后。fast 和 slow 的距离就会增加一。设链表有 n 个元素,那么最多移动 n/2 轮。当 n 为奇数时,slow 恰好指向中间结点,当 n 为 偶数时,slow 恰好指向中间两个结点的靠前一个
六、求两个链表相交位置
160. 相交链表
思想:创建两个指针 pA 和 pB,分别初始化为链表 A 和 B 的头结点。然后让它们向后逐结点遍历。
当 pA 到达链表的尾部时,将它重定位到链表 B 的头结点 (你没看错,就是链表 B); 类似的,当 pB 到达链表的尾部时,将它重定位到链表 A 的头结点。
若在某一时刻 pA和 pB相遇,则 pA/pB 为相交结点。
想弄清楚为什么这样可行, 可以考虑以下两个链表: A={1,3,5,7,9,11} 和 B={2,4,9,11},相交于结点 9。 由于 B.length (=4) < A.length (=6),pB 比 pA少经过 2 个结点,会先到达尾部。将 pB重定向到 A 的头结点,pA重定向到 B 的头结点后,pB 要比 pA多走 2 个结点。因此,它们会同时到达交点。
如果两个链表存在相交,它们末尾的结点必然相同。因此当 pA/pB 到达链表结尾时,记录下链表 A/B 对应的元素。若最后元素不相同,则两个链表不相交。

/**
 * 已经定义的 ListNode 结点
 * typedef struct Node {
 *     int val;
 *     struct Node *next;
 * } ListNode;
 */
ListNode* findIntersectionListNode(ListNode *head1, ListNode *head2) {
    if (head1 == NULL || head2 == NULL) {
		return NULL;
	} else {
		 ListNode *lA = head1;
		 ListNode *lB = head2;		
		while (1) {	
			if(lA == lB) { 
				return lA;
			}
			if (lA == NULL) {
				lA = head2;
			}	
			if (lB == NULL) {
				lB = head1;
			}
		
			lA = lA->next;
			lB = lB->next;
		}
		return NULL;
	}
}

参考:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-ii-by-leetcode/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值