代码随想录算法训练营第四天| 24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,160. 链表相交,142.环形链表II

 题目与题解

24. 两两交换链表中的节点

题目链接:24. 两两交换链表中的节点

代码随想录题解:24. 两两交换链表中的节点

视频讲解:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点_哔哩哔哩_bilibili

解题思路:

        同样,由于这道题涉及到链表增删,设置虚拟头结点有助于简化后续的操作。

        题目思路很简单,假设当前结点为cur,要交换的结点分别是cur.next和cur.next.next,为了方便记忆,这两个结点起名为n1,n2,并将n2.next起名为n3。

        那么,实际操作的顺序是,使cur.next=n2,n2.next=n1,最后n1.next=n3,n1和n2就交换完成了,而n1前和n2后的结点顺序不会发生改变。接着向后移动两次cur结点,重复上述操作。

class Solution {
    public ListNode swapPairs(ListNode head) {
		ListNode virHead = new ListNode(0, head);
		ListNode cur = virHead;
		while (cur.next != null && cur.next.next != null) {
			ListNode n1 = cur.next;
			ListNode n2 = cur.next.next;
			ListNode n3 = cur.next.next.next;
			cur.next = n2;
			n2.next = n1;
			n1.next = n3;
			cur = n1;
		}
		return virHead.next;
    }
}

看完代码随想录之后的想法 

        思路是一致的,链表题画图非常重要,贴上代码随想录的图方便理解。

        随想录给出的代码会更简化一点,不过对我来说容易搞错,所以我还是另外设置了几个变量用于记录。

遇到的困难

        脑子清醒,图一次画对,没有困难:)

19.删除链表的倒数第N个节点

题目链接:​​​​​​​19.删除链表的倒数第N个节点

代码随想录题解:​​​​​​​19.删除链表的倒数第N个节点

视频讲解:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点_哔哩哔哩_bilibili

解题思路:

        基础思路就是先遍历一遍链表,记录链表长度len,那么倒数第n个结点就是正数第len-n个结点,删除即可。同样涉及到增删,用虚拟头结点更方便。       

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
		// 两次遍历
		int len = 0;
		ListNode cur = head;
		while (cur != null) {
			++len;
			cur = cur.next;
		}
		ListNode virHead = new ListNode(0, head);
		cur = virHead;
		for (int i = 0; i < len - n; i++) {
			cur = cur.next;
		}
		cur.next = cur.next.next;
		return virHead.next;
	}
}

        题目要求用一遍扫描实现,这时只需要再设置前后两个结点,当前结点从头向后走了n步后,后一个结点再出发,二者同时前进,直到前面的结点走到了链表末尾,后面结点所在位置就是要删除的结点位置。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
		// 一次遍历
		ListNode virHead = new ListNode(0, head);
		ListNode p1 = virHead;
		for (int i = 0; i < n; i++) {
			p1 = p1.next;
		}
		ListNode p2 = virHead;
		while (p1.next != null) {
			p1 = p1.next;
			p2 = p2.next;
		}
		p2.next = p2.next.next;
		return virHead.next;
	}
}

看完代码随想录之后的想法 

        写链表时,需要注意到合适的边界条件,要对当前结点进行操作时,很多时候要通过前一个结点进行。我做的时候为了方便,都是写完之后用一个例子来判断,遍历的边界有没有超出,当前元素是否正确。这是有点投机取巧的方法,对于简单过程很有效。

        我写的时候看到题目说n>=1小于等于链表长度,所以在前一个结点遍历时没有判断其是否为空,面试时有的时候不会给你这么多这么好的边界条件,所以平时能加上要尽量加上。      

遇到的困难

        这道题以前做过很多遍了,还记得,不难。

160. 链表相交

题目链接:​​​​​​​160. 链表相交

代码随想录题解:​​​​​​​160. 链表相交

解题思路:

        如果两个链表最后相交,那么从相交的结点开始,其后面的所有结点都应该相同。这里可以设置两个指针,分别去遍历两个链表,得到两个链表的长度lenA和lenB;然后一个指针再从长一点的链表从头出发,走了|lenA-lenB|步后,另一个指针再从短一点的链表头出发,两者同步前进,直到遍历得到的结点相同则返回当前结点;如果没有相同结点,指针就会行进到链表的末尾,返回null即可。

        因为不知道lenA和lenB哪个长,为了方便调用,写了一个新的函数getNode专门用来处理。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lenA = 0;
		ListNode cur = headA;
		while (cur != null) {
			++lenA;
			cur = cur.next;
		}
		int lenB = 0;
		cur = headB;
		while (cur != null) {
			++lenB;
			cur = cur.next;
		}
		if (lenA > lenB) return getNode(lenA - lenB, headA, headB);
		else return getNode(lenB - lenA, headB, headA);
    }
	public ListNode getNode(int n, ListNode longList, ListNode shortList) {
		ListNode p1 = longList;
		for (int i = 0; i < n; i++) {
			p1 = p1.next;
		}
		ListNode p2 = shortList;
		while (p1 != null && p1 != p2) {
			p1 = p1.next;
			p2 = p2.next;
		}
		return p1;
	}
}

看完代码随想录之后的想法 

        思路一致,写法上随想录根据情况交换A和B,固定了A是更长的链表,代码更少,不需要额外的函数,内存占用更少一点。

遇到的困难

        这道题同样以前做过无数次了,思路非常清晰,所以写起来也很容易。不断的重复就能加深记忆!

142.环形链表II 

题目链接:​​​​​​​142.环形链表II 

代码随想录题解:​​​​​​​142.环形链表II 

视频讲解:把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II_哔哩哔哩_bilibili

解题思路:

        环形链表的解题思路,一般是先判断是否有环,然后根据环形链表的特征,写出相应的数学公式,再落实到代码上处理。

        判断是否有环很简单,设置两个指针fast和slow,二者同时从头出发,slow一次走一步,fast一次走两步,如果链表中存在环,那二者一定会相遇。

        但是这道题要求找到环形链表的入口,自己写了半天公式也写不对,只能去看代码随想录的答案。其实代码本身非常容易,但是公式难求。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head, fast = head;
		while (fast != null && fast.next != null) {
			slow = slow.next;
			fast = fast.next.next;
			if (slow == fast) break;
		}
		if (fast == null || fast.next == null) return null;
		ListNode index1 = head, index2 = slow;
		while (index1 != index2) {
			index1 = index1.next;
			index2 = index2.next;
		}
		return index1;
    }
}

看完代码随想录之后的想法 

        最重要的是如何根据快慢指针跑的速度和相遇的位置,来判断入口究竟有多远。这张图是一个很好的参考。

        快慢指针相遇时,快指针跑了x+y+n(y+z)步,n表示快指针重复跑的圈数,慢指针跑了x+y步,而快指针实际走的路程是慢指针的两倍,即2(x+y),那么

        x+y+n(y+z)=2(x+y)

我们要求的是x,那么变换一下公式,得到x的表达式

        x=(n-1)(y+z)+z

y+z就是一圈,一个指针在环中走了n圈,其所在结点还是它出发的位置。那么假设一个指针从头走了x步到达环形入口,就相当于另一个指针从相遇结点出发后,走了(n-1)圈加上z步的所在位置。此时就可以设置两个指针,一个从头出发,一个从相遇结点出发,它们最后会在入口相遇。

        关于慢指针为什么只跑了x+y,而不是x+y+若干环的长度,我一开始也不太理解。解答也是很有数学之美。由于fast更快,当slow进环时,fast必然已经在环里面了。假设环长度是l,fast在slow进环时t距离环入口k步,k<l。接着,当fast第二次走到环入口时,fast走了k+l步,此时slow走了(k+l)/2步,结果一定小于l,也就是说,fast第二次走到环入口之前,必然碰到过slow。

        或者换个角度想,当slow进环后,如果slow走了一圈再次到达入口,此时slow走了l步,fast走了2l步,也就是说fast已经套圈slow了,二者肯定遇到过。

        这道题非常有巧思,有点难想,记住吧。

遇到的困难

        这道题也不止做过一遍,但是没有想出来公式究竟应该怎么写。即使这次看完随想录的答案,我也不敢保证说下一次碰到能想出正确的结果。只能记住了。

今日收获

        今天的题都是以往做过很多次的,所以写起来难度不大,但前提是必须知道正确的解法。实际做题大概两小时,环形链表花了一个小时也没有做出来,有点沮丧。

        到今天为止,链表的基础题就算过了一遍。链表的tips总结如下:

  • 涉及到链表增删的,巧用虚拟头结点
  • 遍历链表时注意边界条件,防止空指针异常
  • 想清楚究竟应该从哪个结点开始操作, 是pre还是cur还是next
  • 双指针法在链表中适用于只想遍历一或几次的情况
  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值