目录
一、相交链表
代码详细思路:(目的是找到两个链表的交点)
1. 统计链表长度:
-
首先,代码分别遍历两个链表
headA
和headB
,并统计它们的长度sizeA
和sizeB
。 -
遍历过程中,
l1
和l2
指针分别指向当前节点,并不断向后移动,直到到达链表的末尾。
2. 调整指针位置:
-
代码计算两个链表长度的差值
gap
,并让较长的链表指针先走gap
步。 -
这步操作的目的是为了让两个指针在同一个起点开始比较,以便找到交点。
-
如果
sizeA
小于sizeB
,则将headB
作为长链表,headA
作为短链表。
3. 同时遍历两个链表:
-
现在,
longList
和shortList
指针分别指向调整后的长链表和短链表的起点。 -
代码使用
while
循环同时遍历两个链表,直到其中一个链表遍历结束。 -
在遍历过程中,比较
longList
和shortList
是否指向同一个节点:-
如果指向同一个节点,则说明找到了交点,返回
longList
。 -
如果没有找到交点,则继续向后遍历,将
longList
和shortList
指针分别移动到下一个节点。
-
4. 处理无交点情况:
-
如果
while
循环结束,仍然没有找到交点,则说明两个链表没有交点,返回NULL
。
运行代码:
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
ListNode* l1 = headA;
ListNode* l2 = headB;
int sizeA = 0,sizeB = 0;
while(l1)
{
sizeA++;
l1 = l1->next;
}
while(l2)
{
sizeB++;
l2 = l2->next;
}
//求两者相差数的绝对值
int gap = abs(sizeA-sizeB);
//让长链表先走gap步
ListNode* longList = headA;
ListNode* shortList = headB;
if(sizeA <sizeB)
{
longList = headB;
shortList = headA;
}
while(gap--)
{
longList = longList->next;
}
//此时longList和shortList指针在同一起跑线上
//链表相交 与 链表不相交 两种情况
while(longList && shortList)
{
if(longList == shortList)
{
//链表相交
return longList;
}
else
{
//继续往后走
longList = longList->next;
shortList = shortList->next;
}
}
//链表不相交
return NULL;
}
};
总结:
这段代码通过计算链表长度、调整指针位置和同步遍历两个链表的方式,实现了查找两个链表交点的功能。
代码的优点:
-
思路清晰,易于理解。
-
效率较高,时间复杂度为 O(m + n),其中 m 和 n 分别为两个链表的长度。
代码的不足:
- 没有考虑链表为空的情况,需要在代码中添加判断。
改进建议:
-
在代码开头添加判断,如果
headA
或headB
为空,则直接返回NULL
。 -
可以使用更简洁的代码来实现指针的调整,例如:
cppListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { if (headA == nullptr || headB == nullptr) { return nullptr; } ListNode *l1 = headA; ListNode *l2 = headB; while (l1 != l2) { l1 = (l1 == nullptr) ? headB : l1->next; l2 = (l2 == nullptr) ? headA : l2->next; } return l1; }
二、环形链表I
代码详细思路:(目的是判断一个单链表是否包含环路)
1. 初始化快慢指针:
-
代码首先定义两个指针
slow
和fast
,分别指向链表的头部head
。 -
slow
指针每次移动一步,而fast
指针每次移动两步。
2. 循环遍历链表:
-
代码使用
while
循环,条件是fast
指针不为空且fast
指针的下一个节点也不为空。 -
在循环中:
-
slow
指针每次移动一步,指向下一个节点。 -
fast
指针每次移动两步,指向下一个节点的下一个节点。 -
每次移动后,代码判断
slow
和fast
指针是否指向同一个节点。
-
3. 判断是否包含环路:
- 如果
slow
和fast
指针指向同一个节点,则说明链表包含环路,返回true
。
4. 处理无环路情况:
- 如果
while
循环结束,仍然没有找到slow
和fast
指针指向同一个节点,则说明链表不包含环路,返回false
。
原理:
-
快慢指针算法的原理是,如果链表包含环路,那么快指针最终会追上慢指针。
-
这是因为快指针在环路中不断绕圈,而慢指针在环路中缓慢移动。
-
当快指针绕圈的次数是慢指针移动次数的倍数时,两个指针就会相遇。
代码的优点:
-
思路简洁,易于理解。
-
效率较高,时间复杂度为 O(n),其中 n 为链表的长度。
-
空间复杂度为 O(1),因为只使用常数大小的额外空间。
总结:
这段代码使用快慢指针算法,通过判断快慢指针是否相遇来判断链表是否包含环路。该算法简单高效,是判断链表是否包含环路的一种常用方法。
运行代码:
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head)
{
//定义快慢指针
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
return true;
}
}
//跳出循环的话,两个指针始终没有相遇
return false;
}
三、环形链表II
代码详细思路:(检测单链表中是否存在环路,如果存在环路,则返回环路的入口节点,否则返回 NULL)
1. 初始化快慢指针:
-
代码首先定义两个指针
slow
和fast
,分别指向链表的头部head
。 -
slow
指针每次移动一步,而fast
指针每次移动两步。
2. 循环遍历链表:
-
代码使用
while
循环,条件是fast
指针不为空。 -
在循环中:
-
slow
指针每次移动一步,指向下一个节点。 -
判断
fast
指针的下一个节点是否为空,如果为空,则说明链表没有环路,返回NULL
。 -
fast
指针每次移动两步,指向下一个节点的下一个节点。 -
每次移动后,代码判断
fast
和slow
指针是否指向同一个节点。
-
3. 处理存在环路的情况:
-
如果
fast
和slow
指针指向同一个节点,则说明链表包含环路。 -
为了找到环路的入口节点,代码定义一个新的指针
ptr
,指向链表的头部head
。 -
然后,代码同时移动
ptr
和slow
指针,直到两个指针指向同一个节点。 -
当两个指针指向同一个节点时,该节点就是环路的入口节点,返回
ptr
。
4. 处理不存在环路的情况:
- 如果
while
循环结束,仍然没有找到fast
和slow
指针指向同一个节点,则说明链表不包含环路,返回NULL
。
原理:
-
快慢指针算法的原理是,如果链表包含环路,那么快指针最终会追上慢指针。
-
这是因为快指针在环路中不断绕圈,而慢指针在环路中缓慢移动。
-
当快指针绕圈的次数是慢指针移动次数的倍数时,两个指针就会相遇。
-
当两个指针相遇后,代码通过同时移动
ptr
和slow
指针,找到环路的入口节点。
代码的优点:
-
思路简洁,易于理解。
-
效率较高,时间复杂度为 O(n),其中 n 为链表的长度。
-
空间复杂度为 O(1),因为只使用常数大小的额外空间。
总结:
这段代码使用快慢指针算法,通过判断快慢指针是否相遇来判断链表是否包含环路,并通过同时移动两个指针来找到环路的入口节点。该算法简单高效,是判断链表是否包含环路并找到环路入口节点的一种常用方法。
运行代码:
cpp
struct ListNode* detectCycle(struct ListNode* head)
{
struct ListNode *slow = head, *fast = head;
while (fast != NULL)
{
slow = slow->next;
if (fast->next == NULL)
{
return NULL;
}
fast = fast->next->next;
if (fast == slow)
{
struct ListNode* ptr = head;
while (ptr != slow)
{
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}
return NULL;
}
四、随机链表的复制
代码详细思路:
这段代码的功能是复制一个包含随机指针的链表。它使用了一种巧妙的策略,将复制过程分为三个阶段,并使用快慢指针来进行操作。
1. 创建节点函数 buyNode
:
-
该函数用于创建一个新的节点,并初始化其值 (
val
) 和指针 (next
和random
)。 -
malloc
函数用于分配内存空间,sizeof(Node)
用于计算节点的大小。 -
newnode->val = x
用于将输入值x
赋值给新节点的值。 -
newnode->next = newnode->random = NULL
用于初始化新节点的指针为 NULL。
2. 插入节点函数 AddNode
:
-
该函数用于在原链表中每个节点之后插入一个新节点,新节点的值与原节点相同。
-
它使用一个循环遍历原链表,并对每个节点执行以下操作:
-
创建一个新的节点
newnode
,其值与当前节点pcur
相同。 -
将当前节点
pcur
的下一个节点指向新节点newnode
(pcur->next = newnode
)。 -
将新节点
newnode
的下一个节点指向原链表中下一个节点 (newnode->next = next
)。 -
更新当前节点
pcur
为原链表中的下一个节点 (pcur = next
)。
-
3. 复制随机链表函数 copyRandomList
:
-
该函数用于复制一个包含随机指针的链表。它使用三个阶段来完成复制:
-
第一阶段:复制节点:
-
首先,它调用
AddNode(head)
函数,在原链表中每个节点之后插入一个新节点,从而复制了原链表中的所有节点。 -
此时,原链表中每个节点和它的复制节点都相邻。
-
-
第二阶段:复制随机指针:
-
它使用一个循环遍历原链表,并对每个节点执行以下操作:
-
获取当前节点的复制节点 (
copy = pcur->next
)。 -
如果当前节点的随机指针不为空 (
pcur->random != NULL
),则将复制节点的随机指针指向原节点随机指针的复制节点 (copy->random = pcur->random->next
)。 -
更新当前节点为复制节点的下一个节点 (
pcur = copy->next
)。
-
-
-
第三阶段:断开链表:
-
它使用一个循环遍历原链表,并对每个节点执行以下操作:
-
获取当前节点的复制节点 (
pcur->next
)。 -
将复制节点的下一个节点指向当前节点的下一个节点的复制节点 (
newTail->next = pcur->next
)。 -
更新复制链表的尾节点 (
newTail = newTail->next
)。
-
-
最后,返回复制链表的头部 (
newHead
)。
-
-
代码的优点:
-
代码简洁易懂,易于理解。
-
它巧妙地利用了原链表和复制链表节点的相邻关系,通过遍历一次原链表就能完成复制过程,避免了多次遍历,提高了效率。
总结:
这段代码使用了一种高效的策略,通过三个阶段的处理,成功复制了包含随机指针的链表,并保留了原链表的结构和数据。
运行代码:
cpp
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
typedef struct Node Node;
Node* buyNode(int x)
{
Node* newnode = (Node*)malloc(sizeof(Node));
newnode->val = x;
newnode->next = newnode->random = NULL;
return newnode;
}
void AddNode(Node* phead)
{
Node* pcur = phead;
while(pcur)
{
Node* next = pcur->next;
//创建新结点,尾插到pcur
Node* newnode = buyNode(pcur->val);
pcur->next = newnode;
newnode->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)
{
copy->random = pcur->random->next;
}
pcur = copy->next;
}
//第三:断开链表
pcur = head;
Node* newHead,*newTail;
newHead = newTail = pcur->next;
while(pcur->next->next)
{
pcur = pcur->next->next;
newTail->next = pcur->next;
newTail = newTail->next;
}
return newHead;
}