链表力扣例题解析

前言:写这个的目的主要是让自己能真正理解(不是因为元旦孤独一人在宿舍闲的啊= =)

第一题:反转链表

思路解析:

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;//没有则返回空
}

 再次印证了那句:巧妙的分析才是制胜的关键^ ^

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值