小镇做题家一链表刷题总结(基础篇)

前言

每次的刷题我都会把市面上所有的题都刷一遍,并把高度类似的题放在一起,写了自己的总结(引言部分都是我相对大家说的话和总结)大家要是想看最后总结的话可以直接看每道题的后面,方便大家食用。同时我也写了自己的经验及刷题过程的方法和解题思路。

目录

前言

一、链表基本操作

①反转链表(头插法)

二、双指针

②找到链表的中间节点

1.暴力解法

2.双指针

③求链表倒数第k个节点

1.暴力解法

2.双指针

④求链表第一个公共节点(追及相遇)

1.暴力解法

2.优化

⑤判断链表是否有环(追及相遇)

⑥求链表环内的入口(追及相遇、数学推理)



一、链表基本操作

①反转链表(头插法)

💡对于链表的反转,我们之前在学实现链表的操作的时候,我们有写过头插法的实现,不懂的可以看看我这一篇文章:https://blog.csdn.net/qq_73752061/article/details/139098232?spm=1001.2014.3001.5502          既然学过头插法,那么这题也就不难了。对于链表的反转本质,无非就是头插法的实现,因为头插法的就是就是将链表倒序排列的,所以要实现链表的反转,也就将链表倒序过来就可以实现链表的反转。

 public ListNode reverseList() {
        if(head == null) return null;
        if(head.next == null) return head;
        //cur从第二个节点开始
        ListNode cur = head.next;
        //第一个节点next置为空
        head.next = null;

        while(cur != null) {
            //记录下来 当前需要翻转的节点的下一个节点
            ListNode curNext = cur.next;
            cur.next = head;
            head = cur;
            cur = curNext;
        }
        return head;
    }


二、双指针

②找到链表的中间节点


1.暴力解法

两个for循环遍历两遍列表,第一遍先找出链表的长度,第二遍用链表的长度除以二即可找出。

❓那有什么方法可以遍历一遍就找出链表中间节点吗

💡暴力解法第一遍是遍历出了链表长度,而第二遍才找到了中间位置。那我们怎么样可以一遍遍历出呢?

2.双指针

那我们就可以模拟暴力解法,定义两个指针一个快指针,一个慢指针,让快指针先走完,并且走完时,慢指针刚好在链表的中间(一半)。

❓那么我怎么怎么样可以确定在快指针走完时,慢指针走一半呢?

💡刚好两指针路程一样,只要快指针速度是慢指针速度的两倍及每次(时间相同)快指针走两步,慢指针走一步。那么它们(快指针)停下来的位置就是(慢指针)两倍。

这个原理也是很题应用到双指针,找到特殊位置的原理。有了这一原理那么链表题都可以迎刃而解了。

public ListNode middleNode() {
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }


tips:在这里插入一个比较综合的题目

综合题:链表回文


❓要判断一个链表的回文,我们可以先想到数组的回文,数组的回文是怎么做的?

💡定义一个left指针,定义一个right指针;然后left向前(++)right向后(--)然后对比每次数组的内容是否相同,而链表最大的问题,链表是单向的,是不可以倒着遍历的

但是我们有一种方法可以把它给翻转过来(也就是前面所学的链表反转)找到链表的中间节点,然后将后面的链表翻转过来,就可以像数组一样判断回文的方式了。

public boolean chkPalindrome(ListNode head) {
        // 1. 找到中间位置
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //2. 开始翻转
        ListNode cur = slow.next;
        while(cur != null) {
            ListNode curNext = cur.next;//记录一下下一个节点
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }
        //3. 此时翻转完成,开始判断是否回文
        while(head != slow) {
            if(head.val != slow.val) {
                return false;
            }
            if(head.next == slow) {
                return true;
            }
            head = head.next;
            slow = slow.next;
        }
        return true;
    }


③求链表倒数第k个节点

1.暴力解法

仍然是遍历两边,第一遍找出链表个数,再找出倒数第k个节点。

2.双指针

🍬那么既然这样就很简单了,和上题是差不多一样的。问题就是如何找出倒数第k个节点。

💡可以先让快指针先走k-1步,这样快指针和慢指针就相差k个节点。当它们同时走的时候,快指针走完,慢指针也就到了倒数第k个位置。

    public ListNode FindKthToTail(int k) {
        if(k <= 0 || head == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        for (int i = 0; i < k-1; i++) {
            fast = fast.next;
            if(fast == null) {
                return null;
            }
        }
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }


④求链表第一个公共节点(追及相遇)


1.暴力解法

遍历两个链表的每一个节点,比较是否相同。(很显然这种方法是很不可取的)

2.优化

(从题目中找规律)

如果两个链表相交,那么相交点之后的长度是相同的自此,我们需要做的事情是,让两个链表从同距离末尾同等距离的位置开始遍历。
因为相交后两节点长度相同,我们必须消除两个链表的长度差。

⭐️先让长的链表走差值步,然后两个链表一起同频走,两链表相遇点就是公共的节点。

public class Solution {  
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {  
        ListNode pl = headA;  
        ListNode ps = headB;  
        int lenA = getLength(headA);  
        int lenB = getLength(headB);  
        int c = 0;     
        // 让较长的链表先走差值步数  
        if (lenA < lenB) {  
            c = lenB - lenA;  
            for (int i = 0; i < c; i++) {  
                ps = ps.next;  
            }  
        } else {  
            c = lenA - lenB;  
            for (int i = 0; i < c; i++) {  
                pl = pl.next;  
            }  
        }  
          
        // 同时遍历两个链表,直到找到交点或其中一个链表遍历完  
        while (pl != null && ps != null) {  
            if (pl == ps) {  
                return pl;  
            }  
            pl = pl.next;  
            ps = ps.next;  
        }  
          
        // 如果没有交点,返回null  
        return null;  
    }  
     public int getLength(ListNode head) {  
        int length = 0;  
        ListNode current = head;  
        while (current != null) {  
            length++;  
            current = current.next;  
        }  
        return length;  
    }  
}


⑤判断链表是否有环(追及相遇)


💡假设这道题有环,有环的特点是什么?

🌸那就是追及相遇了,下面给你一个场景,你和你的男女朋友在操场跑步,你们都从宿舍出发,而男宝宝一般都会比较快一点,女宝宝都会比较慢一点,然而在一个环里快的追慢的,最终结果是不是会在环里相遇呢?(没错就是追及相遇问题)

而男宝宝就是快指针,女宝宝就慢指针。

⭐️当然这里有很重要的一点就是:不一定是快指针比慢指针快就能在环里相遇,要知道链表是一种特殊的相遇(不是插肩而过的相遇,而是要两个人一起停下来才能算相遇)

所以说有一种情况是不能相遇的:当环里有两个节点的时候,男宝宝先进入操场,女宝宝后进入操场,两个人位于不同位置,两个人的速度为3:1。既下面这种情况:

所以说最好的情况就是两人2:1的速度方式,能一步一步消除差距,从而不会出现跳过的情况。

public boolean hasCycle() {
        if(head == null) return false;
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) {
                return true;
            }
        }
        return false;
    }


⑥求链表环内的入口(追及相遇、数学推理)


假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z

 slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n(y + z),n为fast指针在环内走了n圈才遇到slow指针(假设最快走一圈能遇到女宝宝,n=1), 

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2

(时间相同,速度是两倍,距离也就是两倍)及 (x + y) * 2 = x + y + (y + z)

两边消掉一个(x+y): x + y = (y + z)

所以来说 最终结论就是  x=z,也就是说相遇点离入口点的距离跟出发点跟入口点的距离一样。 

⭐️所以只要当两者相遇的时候,把快指针放在头节点,让他们同时走,再相遇的节点就是入口节点。

public ListNode detectCycle() {
        if(head == null) return null;
        ListNode fast = head;
        ListNode slow = 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;
        }
        fast = head;
        while(fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值