目录
相交链表
第一种思路:判断尾节点地址是否相同,时间复杂度为O(N^2)。
第二种思路:(节点对齐)记录两个链表节点个数,再根据节点差设置两个快慢指针进行next节点比对。时间复杂度O(N)(3N)。
注意:逆置链表达改变了单链表结构(相交时next指向两个节点),行不通。
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode *curA = headA, *curB = headB;
int lenA = 0,lenB = 0;//初始值设为0
while(curA->next)
{
lenA++;
curA = curA->next;
}
while(curB->next)
{
lenB++;
curB = curB->next;
}
//尾不同
if(curB != curA)
return NULL;
//尾相同
int gap = abs(lenB - lenA);//绝对值absoulute
//假设
struct ListNode *longList = headA,*shortList = headB;
if(lenA < lenB)
{
longList = headB;
shortList = headA;
}
while(gap--)//长的先走gap步
{
longList = longList->next;
}
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return longList;
}
在计算长度的时候我们少计算了一个长度,但我们要求的是长度差,所以没有影响。
当我们不知道哪个链表长的时候可以先假设其中一个链表大,另一个小,然后判断它们的长度做出假设失败的修改。
环形链表
这里有点像我们数学里的相遇问题,这里我们可以用快指针走两步,慢指针走一步的方式解决,最终它们终会相遇。
在进入环后,它们之间的距离每次缩小1,最终会相遇。进环后二者距离为N,假设环有M个节点,则N<M(等于时相遇)。所以slow最坏走了接近一圈的时候二者必然相遇。
bool hasCycle(struct ListNode *head) {
struct ListNode* slow = head,*fast = head;
while(fast && fast->next)//注意顺序
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
return true;
}
return false;
}
附加:为什么fast要走两步,slow只用走一步?
假设fast走三步,slow只走一步,假设进环后它们的距离是N,那么它的追击可能会出现两种情况。
N为偶数:N,N-2,N-4 .... 4,2,0
这种情况是可以追上的。
N为奇数:N,N-2,N-4 .... 3,1,-1
当它们的距离为-1,即fast反超slow一步时,此时的距离为M-1(M为环的节点个数)
若M-1为偶数,则可以追上,若M为奇数,则永远追不上。
结论:当fast走2步以上时,需要判断奇偶决定是否可以追上,步步逼近必然能追上。
环形链表Ⅱ
这道题在前面那道题的基础上增加了返回入环的第一个结点(存在环时)。这里有一个前人总结的结论:当快慢指针相遇的时候的指针与头指针一起向后走,它们再次相遇的位置就是入环的位置。为什么呢?我们来解释一下。
有几个注意点:当slow和fast相遇时,fast必然走了一圈以上(fast走的比slow快),然后利用两倍关系,解出L的值,然后根据图示它们一起行动就会在起点相遇。
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* slow = head,*fast = head;
while(fast && fast->next)//注意顺序
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
struct ListNode* meet = slow;
while(meet != head)
{
meet = meet->next;
head = head->next;
}
return meet;
}
}
return NULL;
}
复制随机指针链表
刚开始看这道题目的时候我是一头雾水,不过好在没有放弃~。先来解释一下题目意思吧,题目要求深拷贝(新空间)链表,也就是说我们得一个一个拷贝节点到新的空间并手动将它们链接起来,使其相互独立。题目里加了一个随机指针,这也是本题的难点,不过各路大佬还是有很多办法解决。
这里我们结合目前学的知识,提供下面两种做法:
一:题目规定复制链表中的指针都不应指向原链表中的节点。 记录一个数组arr,初始值为0,每遍历一个节点值自增1,直到找到random所指向的节点,每遍历一个节点下标加1,在新链表上用数组遍历让random指向相应节点(如果random指向空,就直接指向空)。时间复杂度:O(N^2)
二:(深)拷贝节点到原节点的后面。
这样做的好处就是可以在原链表基础上找到节点及对应的random指向,并通过两个节点的next迅速链接起来。
在执行这一步之后,将新链表取下来链接起来并返回头节点。时间复杂度:O(N)
struct Node* copyRandomList(struct Node* head) {
//step1,链接
struct Node* cur = head;
while(cur)
{
struct Node* next = cur->next;
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;//别忘了拷贝值
//链接
cur->next = copy;
copy->next = next;
cur = next;
}
//step2,置random
cur = head;
while(cur)
{
struct Node* copy = cur->next;
if(cur->random == NULL)//注意判空
{
copy->random = NULL;
}
else
{
copy->random = cur->random->next;
}
cur = cur->next->next;
}
//step3,取下链表
cur = head;
struct Node* copyHead = NULL,*copyTail = NULL;
copyHead = copyTail = (struct Node*)malloc(sizeof(struct Node));
copyHead->next = NULL;//一定要置空
// struct Node* copyTail = NULL,*copyHead = NULL;
while(cur)
{
struct Node* copy = cur->next;
struct Node* next = copy->next;
//恢复原链表
cur->next = next;
//尾插
copyTail->next = copy;
copyTail = copyTail->next;
// if(copyTail == NULL)
// {
// copyHead = copyTail = copy;
// }
// else
// {
// copyTail->next = copy;
// copyTail = copyTail->next;
// }
cur = next;
}
struct Node* Next = copyHead->next;
free(copyHead);
return Next;
}
注意每次循环要让cur回到原点。
我在用哨兵节点做的时候还遇到个错误,准确来说是惯性错误,以前用哨兵位都是题目给定的链表,现在自己创建链表如果是个空链表就得手动将next节点置空,否则返回了一个野指针,关键是力扣有时候报错的位置很离谱,你根本不知道哪里错了!