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

发布时间 2023-12-18 14:45:53作者: xiaoni2023

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

● 今日学习的文章链接和视频链接

代码随想录 (programmercarl.com)

题目链接

24. 两两交换链表中的节点 - 力扣(LeetCode)

● 自己看到题目的第一想法

主要是把这个过程想清楚,链表交换的题目主要想明白要动几个指针,指针改变的顺序也要注意,如果有需要就要提前要保存一些指针

public ListNode swapPairs3(ListNode head) {
        //交换两个结点到底要改变几个指针?
        //两个结点之间的指针要反向,并且这对节点的前一个结点要重新指向新结点,这对结点指向后一个结点的指针也会改变
        //所以要提前保存前一个结点和后一个结点
        //既然涉及到前一个结点的操作了,那么就可以构造一个虚拟头节点
        ListNode tmpHead = new ListNode(0, head);
        ListNode cur = head;
        ListNode pre = tmpHead;
        ListNode next;
        while (cur != null && cur.next != null) {
            next = cur.next.next;
            //指针一个一个改
            pre.next = cur.next;
            cur.next.next = cur;
            cur.next = next;
            pre = cur;
            cur = cur.next;//交换之后指针的位置已经相当于往后挪动了
        }
        return tmpHead.next;
    }

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

我的cur指针指向的是当前pairs中的第一个,所以保存的是前后的指针,卡尔用的是pairs前一个指针作为当前指针,保存的是pairs中的第一个后pairs后的一个结点。我还尝试了一下别的改指针顺序,发现至少还是要保存两个结点的

● 自己实现过程中遇到哪些困难

● 今日收获,记录一下自己的学习时长

1h

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

● 今日学习的文章链接和视频链接

 代码随想录 (programmercarl.com)

题目链接

 19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

● 自己看到题目的第一想法

    public ListNode removeNthFromEnd2(ListNode head, int n) {
        //怎么删除倒数第n个结点,关键在于如何判断当前结点是倒数第n个,倒数第n个就是从尾巴往回走n步
        //一般来说,正数第n个很好判断,倒数第n个就需要两个指针了
        //慢指针和快指针之间相差n步,让快指针走到头,慢指针所在的位置就是倒数第n个
        ListNode fast = head;
        ListNode slow=head;
        while (n > 1) {
            fast = fast.next;
            n--;
        }
        ListNode tmpHead = new ListNode(0, head);
        ListNode pre = tmpHead;
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
            pre = pre.next;//保存前一个指针
        }
        pre.next = slow.next;
        return tmpHead.next;
    }

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

指针冗余了,直接让快慢指针相差n+1步就行,快指针走到头,慢指针正好是要删除结点的前一个

         ListNode fast = head;
        // ListNode slow=head;
        while (n > 1) {
            fast = fast.next;
            n--;
        }
        ListNode tmpHead = new ListNode(0, head);
        ListNode pre = tmpHead;
        while (fast.next != null) {
            fast = fast.next;
            // slow = slow.next;
            pre = pre.next;//保存前一个指针
        }
        // pre.next = slow.next;
        pre.next = pre.next.next;
        return tmpHead.next;

● 自己实现过程中遇到哪些困难

对于n个步长的理解,其实就是指针往后移动n次,然后倒数第几个是从倒数第一个开始算的,所以n=1时步长为0;让快指针挪动n步时的循环条件怎么写,还是具体带入两个数值检验一下

● 今日收获,记录一下自己的学习时长

0.5h

 

 LeetCode面试题02.07. 链表相交

● 今日学习的文章链接和视频链接

 代码随想录 (programmercarl.com)

题目链接

 面试题 02.07. 链表相交 - 力扣(LeetCode)

● 自己看到题目的第一想法

    public ListNode getIntersectionNode2(ListNode headA, ListNode headB) {
        //两个链表在一个地方相交,那么链表A的长度假设是a+c,链表B的长度假设是b+c
        //那么A走到头之后再走b步,B走到头之后再走a步;两个指针走的距离一定是相等的,妙不妙
        //妙啊
        if (headA == null || headB == null) {
            return null;
        }
        ListNode a = headA;
        ListNode b = headB;
        int count = 0;
        while (count < 2) {
            if (a == b) {
                break;
            }
            a = a.next;
            b = b.next;
            if (a == null) {
                a = headB;
                count++;
            }
            if (b == null) {
                b = headA;
            }
        }
        if (count == 2) {
            return null;
        }
        return a;
    }

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

卡尔的做法是让两个链表尾端对齐,然后开始移动指针,这样也挺直观的。可以用前面那道题找到链表的倒数第n个结点,不过要先遍历两个链表看哪个更长,长度短的那个链表长度为size,长度长的链表的快指针走size-1步。

● 自己实现过程中遇到哪些困难

我写的时候通过debug,才发现判断条件是有先后顺序的。第一,如果先移动指针再判断两个结点是否相等,就会漏掉头指针。第二,如果先判断指针是否为null再移动到另一个链表头部,再移动指针,这就相当于指针跳过了一个结点;因此需要先移动指针,再判断是否为null,为null就跳到另一个链表的头节点,这样才不会漏掉任何一个结点

总结要点是不能漏掉链表中每个要遍历的结点。

● 今日收获,记录一下自己的学习时长

 1h

 

 LeetCode142.环形链表II

● 今日学习的文章链接和视频链接

 代码随想录 (programmercarl.com)

题目链接

 142. 环形链表 II - 力扣(LeetCode)

● 自己看到题目的第一想法

记得这道题是用快慢指针,但是不知道为什么快慢指针一定会相遇

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

public ListNode detectCycle2(ListNode head) {
        //这道题的主要思路是用快慢指针,一个指针每次走两步,一个指针每次走一步
        //如果有环的话,这两个指针一定会相遇,为什么呢?
        // 因为快指针先进入环中,慢指针后进入环内,那么快指针相对于慢指针的移动速度是一步,快指针一定会追上慢指针
        // 慢指针遍历完一圈环的时候,快指针一定走了两圈了,由于我们应该考虑相对运动速率
        //所以在这个过程中快指针一定会经过慢指针
        //综上,慢指针走过的路程=2(x+y)=快指针走过的路程=x+y+n(y+z)
        //化简一下:x+y=n(y+z)=> x=n(y+z)-y=> x=(n-1)(y+z)+z
        //这说明,一个指针从环内相遇的位置出发,另一个指针从头出发,两者一定会在环的入口相遇
        //我们这里判断的依据就是指针走过的距离一定相等,所以此时指针移动的速率一定要保持一致
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next!=null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                slow = head;
                while (slow != fast) {
                    fast = fast.next;
                    slow = slow.next;
                }
                return fast;
            }
        }
        return null;
    }

● 自己实现过程中遇到哪些困难

主要是要想明白相遇的道理,以及为什么找到相遇点之后,从相遇的地方出发,两个指针一定会在结点的入口相遇

● 今日收获,记录一下自己的学习时长

 1.5h

 

总结

 

  • 链表的存储形式

链表与数组不同,在内存中不是连续的,因此每个结点都有一块区域用来存储下一块结点的地址

  • 链表的增删改查

链表的增删都有需要注意的地方,也即要保留一些指针,并且经常涉及到对于目标位置的前一个结点的指针的改变

因此,对于头结点这种没有前置结点的处理还需要额外进行判断,为了让操作具有统一性,我们在头结点之前保留一个虚拟头结点。

  • 设计链表

这里依旧是为了操作统一,增加一个虚拟头结点作为成员变量。否则增删的操作不统一。而且进行头插法尾插法还要判断当前是不是第一个结点,因为我们需要知道链表的头到底是哪个

  • 反转链表

把链表每一个结点的指针都反向,指向前一个,所以要有前置结点,也要保存后一个结点

  • 两两交换链表

主要要想清楚这个断开指针然后重连的过程

  • 删除倒数第n个

主要在于如何找到这个结点,两个指针相隔n步,一起往后移动

  • 链表相交

一个指针走到头后走到另一个链表的头,用图纸演示会很容易想明白距离上的等量关系

  • 链表的环

为什么从相遇的地方出发一定能与从头出发的指针在环的入口相遇?这道题还比较复杂,所以需要多复习,主要是要想明白一些数学推理的过程