代码随想录二刷 Day04 | 203.移除链表元素,206.反转链表,24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,160.链表相交,142.环形链表II

题目与题解

参考资料:链表基础

注意点:

  • 链表的定义

Java中,链表定义如下

public class ListNode {
    int val;
    ListNode next;
    ListNode(){};
    ListNode(int val){this.val = val;}
    ListNode(int val, ListNode next){this.val = val; this.next = next;}
}

主要注意一下构造函数如何书写

  • 数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

    链表是通过指针域的指针链接在内存中各个节点。

  • 数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。

    链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

203.移除链表元素

题目链接:203.移除链表元素

代码随想录题解:203.移除链表元素

解题思路

        由于头结点也有可能值为val,所以这种情况最好设置虚拟头结点dummy,来避免头结点要额外处理的情况。

        根据链表的删除原理,只要将当前指针指向下下个元素,就相当于删除了当前指针的下个元素。所以设置一个指针p先指向dummy,然后不断遍历判断p.next的值是否是val,是就让p.next = p.next.next,否则p = p.next,直到p.next == null,遍历结束,返回dummy.next即可。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode dummy = new ListNode(0, head);
        ListNode p = dummy;
        while (p.next != null) {
            if (p.next.val == val) p.next = p.next.next;
            else p = p.next;
        }
        return dummy.next;
    }
}

注意点

        第一次写的时候没有把p = p.next放到else条件里面,就一直提醒我有空指针异常,我还以为是dummy定义错了,后来才发现漏条件了,写完务必要自己在纸上画画试试。

206.反转链表

题目链接:​​​​​​​206.反转链表

代码随想录题解:206.反转链表

解题思路

        已经反转的前一个节点为p1, 需要反转的当前指针为p2,对每一截链表的反转分为三步:先用p3记录当前指针的下一个指针,然后将p2指向p1,再将前一个节点和当前节点同时向前移动一格,直到当前指针为空,最后返回p1即可。

        

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode p1 = null, p2 = head;
        while (p2 != null) {
            ListNode p3 = p2.next;
            p2.next = p1;
            p1 = p2;
            p2 = p3;
        }
        return p1;
    }
}

注意点

        还是要在纸上画一下,注意反转之前要记录当前节点原有的下个节点用于遍历,可以将p1直接初始化为null,p2为head,避免对头节点做额外处理。        

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

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

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

解题思路

        这里涉及到对头结点进行处理,所以也要设置一下dummy节点。

        交换的过程在纸上画一下就非常清晰了,假设需要交换的当前节点为cur和它的下一节点cur.next,前一个节点为pre,记录一下下一个可能需要交换的节点next = cur.next.next,操作按新链表的指向顺序,分为三步:pre.next指向cur.next,cur.next指向cur,cur指向next,然后将pre和cur分别更新为cur和next即可进入下一轮遍历,直到cur或cur.next等于空,最后返回dummy.next。

class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode dummy = new ListNode(0, head);
        ListNode pre = dummy, cur = head;
        while (cur != null && cur.next != null) {
            pre.next = cur.next;
            ListNode next = cur.next.next;
            cur.next.next = cur;
            cur.next = next;
            pre = cur;
            cur = cur.next; 
        }
        return dummy.next;
    }
}

 

注意点

        同样还是要画图更加清晰,什么时候断开节点的指向还是很重要的,不能乱。

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

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

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

解题思路

        常规老题,同样可能涉及头节点操作,设置dummy。

        设置快慢指针fast和slow,fast先从 head开始遍历链表,直到走了N步,然后slow从dummy开始和fast同步遍历链表。这样,当fast遍历到链表末尾时,即fast为null时,slow刚好到达倒数第N个节点的的前一个节点,就可以删除slow.next了。 

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        ListNode fast = head;
        while (n-- > 0 && fast != null) {
            fast = fast.next;
        }
        ListNode slow = dummy;
        while(fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        if (slow.next != null)
            slow.next = slow.next.next;
        return dummy.next;
    }
}

注意点

        这里涉及到删除操作,所以slow最后一定要停在要删除的节点的前一个节点位置,所以要么是fast先多走一步,要么是slow慢一步出发,写的时候要注意二者的遍历长度差为N+1。

160.链表相交

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

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

解题思路

        如果链表能够相交,且知道两个链表之间的长度差diff,那么可以让一个指针先从长一点的链表的头结点出发,直到走了diff步后,让另一个指针从短一点的链表头结点出发,二者同步前进,那么两个指针碰头的地方一定是相交点所在位置。如果不相交,最后指针会走到链表末尾,为null。

        代码写起来比较麻烦,还写了一个新的函数避免重复写lenA更大还是lenB更大这种情况。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lenA = 0, lenB = 0;
        ListNode pA = headA, pB = headB;
        while (pA != null) {
            lenA++;
            pA = pA.next;
        }
        while (pB != null) {
            lenB++;
            pB = pB.next;
        }
        if (lenA > lenB) return getIntersectionNode(headA, headB, lenA, lenB);
        else return getIntersectionNode(headB, headA, lenB, lenA);
    }

    public ListNode getIntersectionNode(ListNode headA, ListNode headB, int lenA, int lenB) {
        ListNode pA = headA;
        for (int i = 0; i < lenA - lenB; i++) {
            pA = pA.next;
        }
        ListNode pB = headB;
        while(pA != null && pB != null && pA != pB) {
            pA = pA.next;
            pB = pB.next;
        }
        return pA;
    }
}

注意点

        这道题还有另一种很天才很简洁的写法,想不到,仅供参考。

        分别设置两个指针,同时分别从两个链表的头节点开始遍历,如果其中一个指针遍历到当前链表末尾,就让它从另一个链表的头结点开始继续遍历,直到两个指针相等。相等有两种情况:两个指针都走到了末尾,为null;或存在相交节点,这时就直接返回当前节点即可。

        证明详见官方题解的双指针法,代码如下

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }
}

142.环形链表II

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

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

解题思路

        这题是一个数学问题,沿用环形链表I这题的思路,设置快慢指针fast和slow,fast一次走两步,slow一次走一步,二者同时前进,相遇则说明存在环,否则fast会提前到达链表末尾等于null。

        如果存在环,假设进环前的路程为x,相遇的地方为x+y,环长度为y+z,那么相遇时,slow走了x+y步,fast走了x+n(y+z)+y步,又因为fast的步数为slow的两倍,可以得到2(x+y)=x+n(y+z)+y,变换后得到x = (n-1)(y+z)+z。

        由于在环中无论走多少圈,最后到达的结点都是一样的,所以(n-1)(y+z)可以直接舍去,得到x=z,即此时如果有个节点从head出发,而slow继续前进,二者最终会在环的入口相遇。

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

注意点

        这公式记住就好,知道就是知道,不知道也很难推出来。

今日收获

        除了设计链表没有做,基本把链表都过了一遍,链表题目主要就是要自己画图,注意涉及到增删时哪个节点要断开,连接的时机是什么,有哪些节点需要提前记录,用几个例子试一试。还要注意是否会出现空指针异常,在循环的时候就把=null的条件写对了,就不会出错。

  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值