【刷题day4】链表| 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07.链表相交、142.环形链表II、总结

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

题目链接
文章讲解

思路: 不难,画图模拟两部分操作,即交换结点和更新指针。注意两点:避免断链(交换顺序合理) + 避免空指针异常(明确退出循环的条件)

class Solution {
    public ListNode swapPairs(ListNode head) {
    	if(head == null || head.next == null) return head;
    	
    	ListNode L = new ListNode(-1, head);
    	ListNode pre = L, n1 = head, n2 = head.next;
    	// pre->n1->n2->n3->n4->...
    	// 交换:n1->n3,n2->n1,pre->n2 变成 pre->n2->n1->n3->n4->...
    	// 更新:pre(n1),n1(n3),n2(n4)
    	while(n2 != null) {
    	    //交换
    		n1.next = n2.next;
    		n2.next = n1;
    		pre.next = n2;
    		//更新
    		pre = n1;
    		n1 = n1.next;
    		//注意,若n1此时为null,表示本次交换后只有最后一个结点了,不需要交换跳出即可
    		//若不跳出,n2赋值将会报空指针异常
            if(n1 == null) break;
    		n2 = n1.next;
    	}
    	return L.next;
    }
}

递归版本:

class Solution {
    public ListNode swapPairs(ListNode head) {
        // base case 退出提交
        if(head == null || head.next == null) return head;
        // 获取当前节点的下一个节点
        ListNode next = head.next;
        // 进行递归
        ListNode newNode = swapPairs(next.next);
        // 这里进行交换
        next.next = head;
        head.next = newNode;
        return next;
    }
}

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

题目链接
文章讲解

思路: 双指针的经典应用,如果要删除倒数第n个节点,让fast先移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

删除链表结点的关键在于找到所删结点的前一个结点

步骤

  • 定义fast指针和slow指针,初始指向虚拟头结点;
  • fast首先走n + 1步 ,这样fast和slow之间间隔n个节点,然后同时移动,等fast指向null时,slow最终指向删除节点的上一个节点(方便做删除操作);
  • fast和slow同时移动,直到fast指向末尾;
  • 删除slow指向的下一个节点
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n){
        ListNode L = new ListNode(-1, head);

        ListNode fastIndex = L, slowIndex = L;
        //只要快慢指针距离相差n即可
        for (int i = 0; i <= n  ; i++){//fast先行n+1步
            fastIndex = fastIndex.next;
        }
        while (fastIndex != null){//直到fast最终指向null
            fastIndex = fastIndex.next;
            slowIndex = slowIndex.next;
        }
        //此时 slowIndex 的位置就是待删除元素的前一个位置。
        // n=2,起始:L(fast,slow)->1->2->3->null
        // fast移动后:L(slow)->1->2->3(fast)->null
        // 同时移动后:L->1(slow)->2->3->null(fast)
        slowIndex.next = slowIndex.next.next;
        return L.next;
    }
}

面试题 02.07. 链表相交

题目链接
文章讲解

思路: 注意,交点不是数值相等,而是指针相等。关键思路在于,两条链表若相交,交点之后的部分合二为一。(因为链表节点不能一对多,交点.next只能指向同一个结点)

  1. 哈希集合
    首先遍历链表 headA,并将链表 headA 中的每个节点加入哈希集合中。然后遍历链表 headB,对于遍历到的每个节点,判断该节点是否在哈希集合中:
    • 如果当前节点不在哈希集合中,则继续遍历下一个节点;
    • 如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分,因此在链表 headB 中遍历到的第一个在哈希集合中的节点就是两个链表相交的节点,返回该节点。
    • 如果链表 headB 中的所有节点都不在哈希集合中,则两个链表不相交,返回 null。
class Solution {//HashSet存储结点
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    	Set<ListNode> set = new HashSet<>();
    	ListNode LA = headA, LB = headB;
    	while(LA != null) { //将A链表节点存入hashset
    		set.add(LA);
    		LA = LA.next;
    	}
    	while(LB != null) {
    		if(set.contains(LB)) return LB;
    		LB = LB.next;
    	}
    	return null;
    }
}
  1. 对应位置指针遍历
    由于链表长度可能不同,都从头节点开始的话就无法对照着遍历判断。因此,考虑先将两条链表末尾对齐,求出两个链表长度的差值,让长链表先移动到与短链表相对应的位置,然后一起向后遍历寻找交点。

在这里插入图片描述

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != null) { // 求链表A的长度
            lenA++;
            curA = curA.next;
        }
        while (curB != null) { // 求链表B的长度
            lenB++;
            curB = curB.next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            //1. swap (lenA, lenB);
            int tmpLen = lenA;
            lenA = lenB;
            lenB = tmpLen;
            //2. swap (curA, curB);
            ListNode tmpNode = curA;
            curA = curB;
            curB = tmpNode;
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap-- > 0) {
            curA = curA.next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != null) {
            if (curA == curB) {
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }
}

142.环形链表II

题目链接
文章讲解

思路:

  1. 判断链表是否有环:可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。(在有环的情况下,相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合)

  2. 寻找环的入口:如下图
    在这里插入图片描述
    两指针相遇时,slow指针走过的节点数为: x + y,fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走的圈数,(y+z)为一圈内的节点个数。

    因为fast指针是一步走两个节点,slow指针一步走一个节点,所以 fast走过的节点数是slow走过的节点数二倍: (x + y) * 2 = x + y + n (y + z),即 x + y = n (y + z)

    因为要找环形的入口,那么要求的是x:x = n (y + z) - y

    再从n(y+z)中提出一个 (y+z),整理公式之后:x = (n - 1) (y + z) + z ( n>=1,因为 fast指针至少要多走一圈才能相遇slow指针。

    这个公式说明:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。 因此,当发现 slow 与 fast 相遇时,我们从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null || head.next == null) return null; //一个节点无法成环
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {// 说明有环
                ListNode index1 = fast;
                ListNode index2 = head;
                // 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}

总结

总结文章

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值