java算法day4

  • 删除链表的倒数第N个结点
  • 链表相交
  • 环形链表Ⅱ

删除链表的倒数第N个结点

解法:双指针(快慢指针)
首先一定要有删除结点的思想。所以这个题是用虚拟头结点比较方便。
先上模拟图,然后看流程:
请添加图片描述
这里后移根据不同的想法有不同的后移步数。
这里我比较喜欢移动对应的上的步数。这里图里面的解法是移动n+1步。其实移动n步也可以做。
我写一个移动n步的。
请添加图片描述
我比较喜欢这个图的做法。

流程:
1.初始化fast=dummyhead,slow=dummyhead。
2.先让fast走n步。
3.然后fast和slow一起走。
4.一起走的循环停止条件:fast.next!=null。
5.此时完成删除操作即可,slow.next = slow.next.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 removeNthFromEnd(ListNode head, int n) {
        ListNode dummyhead = new ListNode(0); //创建一个虚拟头结点
        dummyhead.next = head; //把虚拟头结点加在链表前面
        ListNode fast = dummyhead; //快慢指针都指向头结点
        ListNode slow = dummyhead;
        for(int i = 0;i<n;i++){ //让快的指针先走n步
            fast = fast.next;
        }
        while(fast.next!=null){ //两个一起走。注意这个终止条件,按图来。
            fast = fast.next;
            slow = slow.next;
        }

        slow.next = slow.next.next; //删除操作
        return dummyhead.next; //返回链表头结点。
    }
}

链表相交

这里进行一个小总结:虚拟头结点一般是需要做修改操作时才用到。查找一般不用

这个解法很特殊,建议记下来。
也是双指针解法,但是有数学规律。

请添加图片描述
解法总结一句话:

只要两个有相交,A,B同时往后走,A到底(null)后指回B,B到底后指回A。依然同时往后走,最终一定会相遇。

根据这句话代码很容易就写出来了。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    	//之前说的,建议定义迭代指针.各指向每个链表的头结点
        ListNode a = headA;
        ListNode b = headB;
        //终止的条件就是二者相遇。
        while(a!=b){
            if(a==null){
                a = headB; //a走到底了就到B
            }else{
                a = a.next;
            }

            if(b==null){
                b = headA;//b走到底了就到A
            }else{
                b = b.next;
            }
        }
        return a; //最后a=b了。随便返回一个。
    }
}

如果不懂为什么这么干就可以得到相等。可以看看这个数学推导。
请添加图片描述
按刚刚的流程会发现,a走到相交结点走的步数就是a-c+c+b-c = a+b-c
b走到相交结点的步数是b-c+c+a-c = a+b-c
会发现其实a,b沿着这样的路线走到首个公共结点。走的步数是一样的。

所以就可以推出两个一直往后走,然后走到底了a到B开始,b到A开始。肯定会到首个公共结点相遇。因为走的步数是一样的。

补充:这个题我第二次写的时候,发现有个问题我没思考清楚。
那就是链表不相交的时候我没去模拟过这个过程。
请添加图片描述
之前写的逻辑就已经考虑到了不相交的情况。我之前在想,不相交的情况会出现二者都同时指向null的情况吗?
答案是会的。因为二者就算不相交,最后走的步数都是lengthA+lengthB。所以说最后都会停在null这个位置。但是这里必须要注意一个要点,最后这个null必须也要看做一个可踩的点,如果最后的null看做不可踩的点,那么就会出现担心的无限循环的情况,想想也是,如果null不可踩那就永远遇不到二者同时为null。


环形链表Ⅱ

直接看这个题解:
https://leetcode.cn/problems/linked-list-cycle-ii/solutions/12616/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-

里面有一张图,看完我只能说就做出来了。

做法总结:

设置双指针,fast和slow。fast两次从head头结点出发。
第一次:
fast一次走两步,slow一次走一步。如果有环那slow和fast一定会相遇。
第二次:也就是slow和fast相遇了,fast回到head。之后,fast和slow同时走,两个都是每次只走一步。最终fast和slow一定会再相遇。而且相遇的这个点就是环的第一个点。

注意循环条件。
第一次两个相遇,这个循环必须要永真循环。因为这个循环还必须做一个特判。如果fast走下去走到了null。就必须直接return null了。
那么这里就要写个if判断,由于fast要走两步,所以就要判断fastnull || fast.nextnull。并且这样有一个短路的效果。

第二个相遇就是两个无脑后退,while的条件就是slow != fast。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head; //起点都在head
        ListNode slow = head;
        while(true){ //第一个循环
            if(fast == null || fast.next == null){//而且还弄了个短路效果
                return null;  //特判,万一没有环。
                //由于每次都要走两步,这里还有个细节,还防止了head=null的情况,那么就要判断,fast==null,然后再判断下一步,如果fast.next=null。那么就也没必要走了。因为下一次一次走两步,会导致fast = null。所以逻辑是对的。
            }
            fast = fast.next.next; //fast一次走两步
            slow = slow.next;  //slow一次一步
            if(fast == slow){
                break;  //第一次相遇就停下来
            }
        }

        fast = head; //fast回到头结点。
        while(slow!=fast){ //下一次二者相遇的时候就是环的第一个结点
            slow = slow.next; //二者一次走一步
            fast = fast.next;
        }

        return fast; //返回fast和slow都行
        
    }
}

数学推导:
设:链表头部到环入口(不包括环的入口元素) 为a,环有b个结点,则链表总共有a+b个结点。

在第一轮。两个指针分别走了。f和s步。
fast走的步数是slow的两倍。即f=2s。
fast比slow多走了n个环的长度,即f=s+nb(这个自己多想想)。
两式相减得到s=nb。f =2nb。所以可得到。fast和slow分别走了2n和n个环的周长。

如果让指针从链表头部一直往前走并统计步数k,那么所有走到链表入口节点时的步数是k=a+nb,即先走a步到入口节点。之后每绕一圈环(b步)都会再次到入口节点。而目前slow指针走了nb步。因此,我们只要想办法slow再走a步停下来,就可以到环的入口了。

但是我们不知道这个到底是多少,那么此时仍然是双指针法,考虑构建一个指针,此指针需要有这样的性质:和slow同时往前走a步后,两者在入口节点重合。此时显而易见,从哪里走到入口节点需要a步?那就是head头节点。

所以第二次相遇的逻辑就来了。
1.令fast重新指向head。此时f = 0,s = nb。
2.slow和fast同时每轮往前走一步
3.当fast指针走到f = a步时,slow指针就走到了s = a+nb,此时fast和slow在环入口重合。返回slow指向的结点即可。

补充:
我第二次写的时候在if判断那里有点问题,一定要想清楚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值