24. 两两交换链表中的节点
题目:力扣
难点在与如何模拟节点的交换,在编码实现的时候容易出现杂乱而导致循环节点的出现。
在自己实现的时候,出现的错误:
- 把head和head.next作为迭代的基准,但是存在的问题是:在循环内部需要判断head.next是否为null才能判断循环是否可以继续否则报错;涉及到了point、head、head.next三个指针的变换,容易出错,非常麻烦 --> 把point以及head作为迭代的基准 =point后的第一个位置和第二个位置交换顺序,循环的先决条件判断 point后的节点是否为null,point.next.next节点是否为null 【0/1 不用交换】
- 在将节点进行交换之后,需要先把后面缓存的节点和前面交换好的节点先连接起来,再将point 和 head进行移位
public ListNode swapPairs(ListNode head) {if(head == null || head.next == null){return head;}ListNode dummy = new ListNode(0);ListNode point = dummy;dummy.next = head;while(point.next != null && point.next.next != null){ListNode temp = head.next.next;//后面的节点进行缓存point.next = point.next.next;//先确定point后的第一个位置point.next.next = head;//再确定point后的第二个位置head.next = temp;//将缓存接上point = head;//先将point移位head = head.next;//再将head移位}return dummy.next;}
参考资料:代码随想录
19. 删除链表的倒数第N个节点
题目:力扣
第一解题思路:先扫描一遍获得链表长度,然后找到要删除的位置,再进行第二次扫描,删除这个节点。
public ListNode removeNthFromEnd(ListNode head, int n) {ListNode dummy = new ListNode(0);dummy.next = head;ListNode cur1 = dummy;ListNode cur2 = dummy;int size = 0;while(cur1.next != null){cur1 = cur1.next;size++;}int index = size - n;for(int i=0; i<index; i++){cur2 = cur2.next;}cur2.next = cur2.next.next;return dummy.next;}
进阶版本的实现方法:其实不难发现,这两次迭代其实可以合并成一次 - 使用双指针:快指针先走,慢指针慢n步再走,当快指针到了之后,慢指针也到了要删除节点的位置,直接删除节点即可。
注意:使用虚拟指针
public ListNode removeNthFromEnd(ListNode head, int n) {ListNode dummy = new ListNode(0);dummy.next = head;ListNode fast = dummy;ListNode slow = dummy;for(int i=0; i<n; i++){fast = fast.next;}while(fast.next != null){fast = fast.next;slow = slow.next;}slow.next = slow.next.next;return dummy.next;}
参考资料:代码随想录
面试题02.07 链表相交
题目:力扣
注意这里链表相交需要判断的的并不是两个链表中的节点值是否相等,而是地址是否相等;
因此可以想到使用hash集合来判断ListNode这个节点是否是相同的。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {HashSet<ListNode> set = new HashSet<>();while(headA != null){set.add(headA);headA = headA.next;}while(headB != null){if(set.contains(headB)) return headB;headB = headB.next;}return null;}
时间复杂度:O(m+n),其中 m 和 n 是分别是链表 headA 和 headB 的长度。需要遍历两个链表各一次。
空间复杂度:O(m),其中 m 是链表 headA 的长度。需要使用哈希集合存储链表 headA中的全部节点。
更优的办法是采用双指针 - 降低空间复杂度为O(1):
方法一:
要相交的话,若长度不同,则只能在后面相同长度部分相交,所以让长的链表先移动到和短的链表长度相同的位置。
然后判断是否A和B的节点相等,不相等则同时向后移动 - 移动过程无需判断是否为null,因为在长度相同的位置了,就算是null,也是同时为null。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {int lenA = 0;int lenB = 0;ListNode curA = headA;ListNode curB = headB;//计算A长度while(curA != null){curA = curA.next;lenA++;}//计算B长度while(curB != null){curB = curB.next;lenB++;}//初始化指针curA = headA;curB = headB;//若A比B长if(lenA >= lenB){//先将A移动到和B长度一样的位置for(int i=0; i<lenA - lenB; i++){curA = curA.next;}while(curA != curB){//判断node是否一样而不是判断node中的值是否一样curA = curA.next;curB = curB.next;}}else{//先将B移动到和A长度一样的位置for(int i=0; i<lenB - lenA; i++){curB = curB.next;}while(curA != curB){//判断node是否一样而不是判断node中的值是否一样curA = curA.next;curB = curB.next;}}return curA;}
方法二:
若两个链表能相交的话,那么从A点出发再从B点出发,两个指针一定能够相遇。
证明:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if(headA == null || headB == null){return null;}ListNode curA = headA;ListNode curB = headB;//循环以找到A和B相同节点为止 - 若A和B不相交,则相同节点为null节点while(curA != curB){//若A节点走完了,则从B节点继续走if(curA == null){curA = headB;}else{curA = curA.next;}//若B节点走完了,则从A节点继续走if(curB == null){curB = headA;}else{curB = curB.next;}}return curA;}
参考资料:
代码随想录 力扣
142.环形链表
1.判断链表是否有环
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
2.如果有环,如何找到这个环的入口
从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
那么相遇时: slow指针走过的节点数为: x + y
, fast指针走过的节点数:x + y + n (y + z)
,n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 x = z
在实现的时候写while循环的时候犯了低级的bug:while(fast == slow)用来判断是否继续循环 - 用于找到第一次相遇;但是初始 fast和slow都是head循环直接停止。
正确的while循环写法:
public ListNode detectCycle(ListNode head) {//先用快慢指针确定是否有环ListNode fast = head;ListNode slow = head;while(fast != null && fast.next != null){fast = fast.next.next;slow = slow.next;if(fast == slow){//说明有环ListNode cur = head;while(cur != fast){ //找到环入口cur = cur.next;fast = fast.next;}return cur;}}return null;}
参考:代码随想录
链表总结:
代码随想录