前言:写这个的目的主要是让自己能真正理解(不是因为元旦孤独一人在宿舍闲的啊= =)
第一题:反转链表
思路解析:
1.翻指针法:链表是由指针链接而成,所以我们可以将指针的方向翻转即可,同时记得将尾指空,那么我们该如何实现呢
迭代过程:比如本来是1->2 我们要把它翻成 1<-2
定义n1指针指向1的地址,定义n2指针指向2的地址(其实也就是1->next)
代码实现:struct ListNode* n1=head,*n2=n1->next;
(1)将2指向3的地址改为指向1的地址(也就是将1的地址赋给2->next)
代码实现:n2->next=n1;
(2)将1指向2的地址改为指向空的地址
代码实现:n1->next=NULL;
(3)迭代:此时我们已经弄好第一个和第二个元素的一半,接下来就要将n1,n2右移
比如 将n1=n2,n2=n2->next;
你发现了吗?我这个比如是错的,因为n2已经被修改为n1的地址了,你再让n2赋给n2的下一个地址其实是相当于:(n2=n1(原来的n1:并未修改过的n1)->next)那不还是原来的吗,这相当于自己向自己移动,是没有意义的(这个地方会有点绕,你需要有自己的理解)
那我们能不能调换一下顺序呢?我们先让
n2=n2->next;
n1=n2;
你会发现还是不对啊
n2的地址被修改为n2的指向的下一个地址(这个是对的)
n1=n2:你会发现相当于n1,n2指针都指着3这个位置,但是我们还没有进行2->3到3->2的交换,
2个指针都指在同一个地方这个咋办啊,回去又不可能回去(这是单向链表)
所以综上所述2个指针无法完成此任务
呜呜呜,我们可不能就这么放弃啊,编程本就是逆天而行,死在半路很正常
正确解法:
既然2个指针不能完成,我们不妨试试3个指针
(1)定义并初始化这3个指针,定义*n1=NULL,*n2=head,*n3=n2->next;
(2)与上同理本来是 NULL->1 我们要翻转成 1-<NULL(一开始相当于n1指向1的地址,n2指向2的地址)(尾指针的末尾要指空,所以这也是我们定义*n1=NULL的原因)
代码实现:n2->next=n1;
tip:单向链表写成这样就可以了,我在写的时候傻逼了,老是想成双向
(3)迭代:即将这3个指针右移均右移一个单位即可
代码实现:
n1=n2;
n2=n3;
if(n3!=NULL)
{
n3=n3->next;//将n3作为驱动力,如果n3的地址不为空,则指向下一个地址
}
实质是地址的修改
迭代过程写完了:我们还要考虑进来的条件和break循环的条件
循环满足的条件:我们要让最后一个数指向倒数第二个数,然后我们又向右进行移动一位
所以n1地址就是最后一个地址,那么n2的地址就一定是空
代码实现:while(n2!=NULL)
也可以写成:while(n2)
大功告成
个毛
力扣就是恶心在这点:考虑不全,别想通过
我们来分析一波:如果这个指针本身就没有元素,那我们还进什么循环啊,直接return NULL;
完整代码:
第二题:寻找链表的中间节点
分析:这题难在进阶:只能遍历一次,为什么只能遍历一次就难
因为很多人会类比数组求结点,定义一个计数器cnt=0嘛,i++,cnt++
第二次让数组再次遍历,直到i==cnt,该数组元素即是节点
或许你说我数组第二次不用遍历了,直接a[cnt],在数组当然是没有问题,但这里是单链表
不能说你想要这个东西,我直接返回a[i],链表需要从头开始寻找,这就是难点
解析:定义两个指针,快慢指针,慢指针一次走1步,快指针一次走2步,二者初始都为头元素地址
迭代过程有了,我们来分析什么时候终止
当链表的长度为奇数时:当长度为奇数时,fast指针指向最后一个元素即停止
当链表的长度为偶数时:当长度为偶数时,fast指针指向为空时即停止
代码实现:while(fast!=NULL && fast->next!=NULL);
巧妙的分析才是制胜的关键^-^(先想好再冻手)
第三题:合并两个有序链表
分析:这道题与合并数组几乎相同:很简单啊,我们轻车熟路
因为这两个链表都是有序的,又让我们合并为有序的,我们只需搞初始化两个指针分别指向链表的head,然后将head->val进行比较,小的就尾插新链表,尾插好后,将原来指向较小数的指针右移,再进行比较,重复此过程即可
注意:我们这里需要加判断:如果有其中一个链表已经空了(元素都被选完了),我们直接让另外一个链表尾插入新链表即可
struct ListNode*mergeTwoLists(struct ListNode*l1,struct ListNode*l2)
{
//判空
if(l1==NULL)
{
return l2;
}
if(l2==NULL)
{
return l1;
}
struct ListNode*head=NULL,*tail=NULL;//定义两个指针
head=tail=(struct ListNode*)malloc(sizeof(struct ListNode));//开空间
while(l1!=NULL && l2!=NULL)//如果这2个指针其中之一为空,则跳出循环
{
if(l1->val <l2->val)//如果1链表的值小于2链表的值
{
tail->next=l1;//将l1的地址存入尾指针:即尾插
l1=l1->next;//l1指向下一个地址
}
else//反之
{
tail->next=l2;
l2=l2->next;
}
tail=tail->next;//推进,
}
//循环跳出,必有一链表为空
if(l1!=NULL)
{
tail->next=l1;
}
if(l2!=NULL)
{
tail->next=l2;
}
struct ListNode*first=head->next;//定义一个头指针
free(head);//销毁岗哨(头)
return first;//返回首元素的地址
}
第四题:环形链表(简单数学)
这道题运用了简单数学公式:一个指针从首元素开始走,一个指针从相遇点(1)开始走,它们相遇(2)的地方就是进环点!
(1)相遇点:定义快慢指针,慢指针的步长为1,快指针的步长为2(具体解释有机会再写,要熄灯了),因为它们的差为1,所以当快慢指针都在环里时,它们一定会相遇的,由此即可以得出上上面的简单数学公式,感兴趣的同学可以自己先证明^ ^
(2)第二个相遇点就是进环点,也就是题目要求的
struct ListNode*detectCycle(struct ListNode*head)
{
struct ListNode*slow=head,*fast=head;//定义快慢指针
while(fast && fast->next)
{
slow=slow->next;//慢指针步长为1
fast=fast->next->next;//快指针步长为2
if(slow==fast)//如果快慢指针相遇
{
struct ListNode* meet=slow;//定义meet指针,初始位置即为第一次相遇位置
while(head!=meet)//当头指针未与meet指针相遇
{
head=head->next;//下一步
meet=meet->next;//下一步
}
return meet;//返回第二次相遇点
}
}
return NULL;//没有则返回空
}
再次印证了那句:巧妙的分析才是制胜的关键^ ^