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

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

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

        先看的卡哥视频再自己动手做的,自己做跟听还是不一样的,编程学习是需要动手的!     ᕦ(・ㅂ・)ᕤ

遇到的问题:

没搞清第一个临时节点和第二个临时节点的赋值时间

 对临时节点temp0和temp1的理解:

        必须一开始在循环中就把temp0和temp1赋值,然后才开始移动p,才能保证后续临时的temp节点是有效的!不信自己试试o(´^`)o

        第一次写的代码:(先移动p.next之后再赋值temp1)

后面正确的代码:

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode newHead = new ListNode(-1, head);         // 虚构一个头节点
        ListNode p = newHead;                              // p相当于cur
        ListNode temp0 = null;
        ListNode temp1 = null;
        while(p.next != null && p.next.next != null){      // 第一个判断的是偶数个节点的循环终止条件(当然也包括head节点为空时,0就是偶数),第二个判断是奇数个节点的,显然p.next得先判断,不然会空指针异常
            // 显然先想到p.next要指向p.next.next,但是这样,有个问题,p.next原本的指向将会丢失(示例图1中的1),所以,先需要一个临时变量temp0来存储p.next
            // 然后需要让p.next.next指向p.next,但是!!注意!!,不能先修改p.next再来保存p.next.next,不信自己试试o(´^`)o
            // 如果先修改了p.next,p.next.next不就也跟着修改不是原来的值(3)了嘛
            temp0 = p.next;
            temp1 = p.next.next.next;
            p.next = p.next.next;
            p.next.next = temp0;
            temp0.next = temp1;
            // 别忘了移动p!!
            p = p.next.next;        // 移动两格就可以,接着循环操作后面的两个(p在示例图1中2位置(当然此时已经是1了,不用管)下次操作3和4)
        }   
        return newHead.next;
    }
}

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

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

        这道题自己写了两种解法,一种是遍历了两次的方法,一种是自己写的双指针法。这两种方法我都用到了for循环来移动指针。

方法一:我先while循环得到链表长度,然后for循环找到倒数第n个节点的前驱。

方法二:我想象的是,每次都移动fast指针n个位置(这里会用到for循环),如果移动了n个位置之后,fast等于null了,说明原本fast的位置,就是我们要找到倒数第n个节点,然后因为slow指向的就是fast移动之前的位置,所以修改slow的next就完成了删除

        但是在用for循环处理指针移动位置时,对指针所指位置有点懵了。

遇到的问题:

利用for循环结合i下标来移动指针时,对指针位置把控不准确

         先上我方法一的代码吧,之后再来解释这个问题:

1.两次遍历法(单指针法)

代码如下:

public ListNode removeNthFromEnd(ListNode head, int n){
    // 1.单指针法(哈哈,其实是遍历了两次,一次计算长度,一次循环找到倒数第n个节点前一个节点)
        int listLength = 0;
        ListNode newHead = new ListNode(-1,head);
        ListNode p = newHead;
        while(p.next != null){      // 计算链表的长度(不包括虚拟头结点)
            listLength++;
            p = p.next;
        }
        p = newHead;                // 重新回到虚拟节点处,准备查找倒数第n个节点的位置
        for(int i = 0; i< listLength - n; i++){// 找到第n个节点的前驱(记住是用i<)
            p = p.next;
        }
        p.next = p.next.next;
        return newHead.next;
    }

        在这个方法的中,如何让p移动到倒数第n个位置之前?(或者说,for循环结束之后,p真的是在倒数第n个位置之前吗?)这该怎么看呢?下面,通过一张图来辅助理解一下:

 看完这张图,这下理解了:for循环的循环条件,就是步数。如果是<,那么就是n步;如果是<=,那么就是n+1步

        而在我的方法一中,是想要找到倒数第n个节点的前一个节点位置,那么循环条件应该设置为lengthList - n(例如,上图中,lengthList=3,n=2,那么pred移动3-2=1步之后到达n=1这个位置,正好是倒数第2个节点的前一个节点位置)。

        总之,利用for循环解决指针移动问题,关键在于3点:

1.p指针初始位置在哪?

2.根据循环条件判断移动步数

3.画图理解

  2.双指针法(自己实现的,个人认为更好理解)

 代码如下:

public ListNode removeNthFromEnd(ListNode head, int n){
        // 2.双指针法(一次遍历)
        ListNode newHead = new ListNode(-1,head);
        ListNode fast = newHead.next;
        ListNode slow = newHead;
        while(fast != null){             // 当fast这个指针为空了,说明已经找到倒数第n个节点了,此时的slow就是它的前驱
            for(int i =0; i < n; i++){  // 移动n次fast
                fast = fast.next;
            }
            if(fast == null){           // 判断fast是否为空(为空说明移动之前的fast所指节点就是要删除的节点)
                break;                  // 跳出循环,准备删除
            }
            else{                       // fast移动后没有指向空,说明移动之前的fast所指节点不是倒数第n个节点,此时让fast回归到原来的位置
                fast = slow.next;
                // 整体移动slow和fast
                slow = fast;
                fast = fast.next;
            }
        }
        // 退出循环则表示slow的下一个就是要删除的节点
        slow.next =slow.next.next;
        return newHead.next;
    }

        再说一下思路:“我想象的是,每次都移动fast指针n个位置(这里会用到for循环),如果移动了n个位置之后,fast等于null了,说明原本fast的位置,就是我们要找到倒数第n个节点,然后因为slow指向的就是fast移动之前的位置,所以修改slow的next就完成了删除

面试题 02.07. 链表相交

题目链接:面试题 02.07. 链表相交

        思路是代码随想录里的,一开始没有理解两两相交是啥意思,以为数值相等并且后面数字全相等的叫做相交,但是这样示例1相交的点也可以是1啊,然后看了一样随想录里的解释,发现是指针相等的叫做相交,思路就是先求出两个链表的长度,然后以长度更短的为循环次数,整体移动两个链表,并且比较指向两个链表的指针,是否相等,相等了就是相交了,直接返回相等的那个节点,如果循环结束还没返回,说明没有相交的点,返回null即可。

代码如下:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null){
            return null;
        }
        ListNode p = null;
        ListNode pA = headA;
        ListNode pB = headB;
        ListNode newHead = new ListNode(-1);
        int lenghtA = 0;
        int lenghtB = 0;
        // 下面开始计算两个链表的长度
        newHead.next = headA;
        p = newHead;
        while(p.next != null){
            p = p.next;
            lenghtA++;
        }
        newHead.next = headB;
        p = newHead;
        while(p.next != null){
            p = p.next;
            lenghtB++;
        }
        int count = lenghtA<lenghtB?lenghtA:lenghtB;    
            // 尝试移动pA或者pB(只移动长的)
            for(int i = 0; i < lenghtA - count; i++){// 进入循环了,说明lengthA是更长的
                pA = pA.next;
            }
            for(int i = 0; i < lenghtB - count; i++){// 进入循环了,说明lengthB是更长的 
                pB = pB.next;
            }
            // 至此,pA与pB后续链表是一样长度的
            // 开始相互比较,并且整体移动pA和pB
        for(int i = 0; i < count; i++) {            // count作为循环次数,以长度短的为循环次数
            if(pA == pB){
                return pA;
            }else{
                pA = pA.next;
                pB = pB.next;
            }
        }
        // 退出循环还没找到,说明两条链表没有相交,返回null
        return null;
    }

遇到的问题:

没理解相交的含义,还有求长度的时候其实可以不用new一个新的头节点,可以直接:

        while (curA != null) { // 求链表A的长度
            lenA++;
            curA = curA.next;
        }

 142.环形链表II

        自己思考的时候,想到了要用快慢指针,我想的是,快指针先走,如果快指针的next节点是慢指针,则慢指针的位置就是环的入口,可是问题是:快慢指针什么时候移动呢?然后就没思路了。。。看了题解视频,有点似懂非懂,最后在力扣一个题解下面看到一条评论,瞬间就开朗了

力扣题解链接:. - 力扣(LeetCode)

评论区大佬解释:. - 力扣(LeetCode)

结合大佬解释以及我下面的代码,我的理解:

 

 代码如下:

    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;                       // 快慢指针同起点开始移动
        while(fast != null && fast.next != null) {   // 因为快指针后面要走两步,为了没有空指针异常,所以需要fast和fast.next都不为空才行
            fast = fast.next.next;                  // 快指针移动两步
            slow = slow.next;                       // 慢指针移动一步
            // 下面判断快慢指针是否相遇
            if(fast == slow){                       
                ListNode index0 = slow;             // 记录相遇的这个位置
                ListNode index1 = head;              // 记录头节点
                while(index0 != index1) {           // 下面开始让这两个位置同步移动,等到这两个指针相遇的时候,便是环的入口
                    index0 = index0.next;
                    index1 =index1.next;
                }
                return index0;
            }
        }
        return null;
    }

链表章节的总结:

        终于写完链表啦!但是旅途的中点而不是终点 |ू・ω・` )后续的题目慢慢加油吧,这篇打卡还是补卡的〒▽〒,还落了两篇打卡的,加油加油。

        关于链表(个人认为重要的):

1. 链表移动结合for循环中 i 的位置来理解是关键,这篇的“删除链表的倒数第N个节点”和上一篇“设计链表”中都有关于数字下标和指针p的图解,可以帮助理解一下

2. 链表题大多数都设计“循环”、和“无效值的判断”

        对于循环:

                (1). 对第一个节点为空值时能否有效停止(不报空指针异常)每一道题都有循环,独立分析一下循环条件就知道了。

                (2). 对于循环终止条件,什么时候停下,并且也要保证不报空指针异常。

        对于无效值判断:

                往往都是开头就设置if,把异常值排除,如设计链表,还有就是结合循环条件里面,头指针为空,往往也被循环条件包含了,所以即使没有进入循环,返回的空值也是符合提议的,如24. 两两交换链表中的节点中的while循环(当节点数是0,也符合是偶数,没有进入循环,但是返回的值也是正确的

        

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值