代码随想录|节点交换|删除倒数第N个节点|链表相交|环形链表Ⅱ

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

文档讲解:代码随想录
视频讲解:B站视频
状态:没有做出来,在了解了亮亮交换的三个步骤之后做出来了。我认为单这一道题而言,递归的方法更容易理解

首先介绍一下交换节点的一般步骤和迭代法,两两交换节点时,需要改变三个节点的指针,因此需要使用虚拟头节点,完成交换并返回正确的头节点。节点交换的方法可以分为以下三个步骤:

在这里插入图片描述

这种方法在每次迭代时需要三个节点,并且需要改变这三个节点的指针,每次迭代需要前进两个单位的索引。在编写代码的时候需要注意临界条件,奇数链表的最后一个节点不必交换

迭代的代码如下:

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode virhead = new ListNode(-1, head);
        var index = virhead;
        var first = index.next;
        if (first == null || first.next == null) return virhead.next;
        var second = index.next.next;
        var temp = second.next;
        while (second != null) {
            // 三次指针交换
            index.next = second;
            first.next = second.next;
            second.next = first;
            //更新三个指针
            if (first.next == null || first.next.next == null) break;
            index = first;
            first = first.next;
            second = first.next;
        }
        return virhead.next;
    }
}

递归的方法就不必关系节点的索引移动了,递归通常考虑以下几个问题

  • 递归的退出条件
  • 递归每次传入函数的参数变化
  • 每一个子问题中应该完成的工作

在这个问题中,递归的要素如下:

  • 当传入头节点为空或者头节点的下一个节点为空时,即遍历完成或者只剩最后一个节点时,函数退出
  • 需要交换的节点的前一个节点作为头节点
  • 与迭代法每次循环完成的工作一致,交换节点。

递归方法的代码如下,相比起迭代方法,空间复杂度为O(n)。

过程中遇到的问题大多是临界条件的问题。

class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) return head;
        var first = head;
        var second = head.next;
        var newnode = swapPairs(second.next);

        second.next = first;
        first.next = newnode;
        return second;
        }
    }

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

文档讲解:代码随想录
视频讲解:B站视频
状态:AC

这道题较为简单,可以使用两次循环,先得到链表长度,再将倒排索引转换为正排索引解决。
也可以使用双指针,前一个指针延迟后一个指针N个索引,遍历一次链表解决;

注意边界条件为:前一个指针需要指向链表最后一个元素而不指向空,代码如下:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode virhead = new ListNode(-1, head);
        var fast = virhead;
        var slow = virhead;
        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 virhead.next;
    }
}

160. 链表相交

文档讲解:代码随想录
状态:由于两个链表的长度不同,第一次刷无从下手,看文档后得知解决方法

使用双指针的方法如下:

  • 使用两个指针指向两个链表头,然后遍历得到两个链表的长度
  • 根据长度同步两个链表的指针,让它们有可能同时指向相交节点
  • 遍历节点,找到相交节点,遍历结束后没找到则返回null

代码如下:

class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        var indexA = headA;
        int sizeA = 0;
        var indexB = headB;
        int sizeB = 0;
        while (indexA != null) { // 得到A链表长度
            sizeA++;
            indexA = indexA.next;
        }
        while (indexB != null) { // 得到B链表长度
            sizeB++;
            indexB = indexB.next;
        }
        if (sizeA > sizeB) {
            int offset = sizeA - sizeB;
            for (int i = 0; i < offset; i++) {
                headA = headA.next;
            }
        }
        else {
            int offset = sizeB - sizeA;
            for (int i = 0; i < offset; i++) {
                headB = headB.next;
            }
        }  // 完成指针同步,两指针会一同指向汇聚节点或者一同指向null
        while (headA != null && headA != headB) { //这个条件是表示若存在相交节点触发第二个条件,否则触发第一个条件
            headA = headA.next;
            headB = headB.next;
        }
        
        return headA; 
    }
}

另外,代码随想录网站给出更简洁的方法,有空再看

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
		// p1 指向 A 链表头结点,p2 指向 B 链表头结点
		ListNode p1 = headA, p2 = headB;
		while (p1 != p2) {
			// p1 走一步,如果走到 A 链表末尾,转到 B 链表
			if (p1 == null) p1 = headB;
			else            p1 = p1.next;
			// p2 走一步,如果走到 B 链表末尾,转到 A 链表
			if (p2 == null) p2 = headA;
			else            p2 = p2.next;
		}
		return p1;
    }
}

142. 环形链表Ⅱ

文档讲解:代码随想录
视频讲解:B站视频
状态:可以使用快慢指针检测出是否存在环,但是无法判断入环节点,看文档学会了,但是需要复习。

这个问题可以分解为两个问题

  • 如何检测是否存在环
  • 如何检测出环的入口

首先第一个问题,快慢指针可以解决,这个视频讲的很形象,最后得到的结论是,若有环,必然存在两个指针指向同一个节点。

第二个问题:如何找到入口节点,这一部分需要一些数学推导,画一个图说明

在这里插入图片描述

假设环的长度cirlen = index + z,其中index表示快慢指针相遇的位置相比环入口的偏移
pos表示环入口相对头节点的偏移,则可以得知快慢指针相遇时, fast走过2倍于slow的路程
(pos + index) * 2 = n(index + z) + pos + index,,提出pos得到pos = (n-1)(index + z) + z

以上公式表示可以用环的长度和慢指针的位置得到环入口位置。也就是说,如果两个指针同步移动,当一个指针偏移了pos时,一个位于环中的指针正好从慢节点位置便宜到入口节点,此时两个指针指向同一个节点,代码如下:

class Solution {
    public ListNode detectCycle(ListNode head) {
        var fast = head;
        var slow = head;
        int counter = 0;
        byte flag = 0;
        // 检测是否存在环
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            counter++;
            if (fast == slow) {
                flag = 1;
                break;
            }
        }
        // 如果存在环,则寻找入口
        while (flag == 1) {
            if (head == slow) return head;
            slow = slow.next;
            head = head.next;
        }
        return null;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值