• 链表面试题(图文详解)


    在这里插入图片描述

    🎉🎉🎉写在前面:
    博主主页:🌹🌹🌹戳一戳,欢迎大佬指点!
    博主秋秋:QQ:1477649017 欢迎志同道合的朋友一起加油喔💪
    目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个小菜鸟嘿嘿
    -----------------------------谢谢你这么帅气美丽还给我点赞!比个心-----------------------------

    在这里插入图片描述



    1,删除链表中所有等于定值val的节点

    Leetcode传送门,点击这里!!

    class Solution {
        public ListNode removeElements(ListNode head, int val) {
            if(head == null){
                return null;//链表为空
            }
            ListNode prev = head;
            ListNode cur = head.next;
            while(cur != null){
                if(cur.val == val){
                    cur = cur.next;
                    prev.next = cur;
                }else{
                    prev = cur;
                    cur = cur.next;
                }
            }
            //特殊情况,头节点也为key
            if(head.val == val){
                head = head.next;
            }
            return head;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这个题的解答其实和我们的单链表的模拟实现的removeAllKey()是一个意思,所以具体的解析大家看这张图。


    在这里插入图片描述


    2,反转一个单链表

    Leetcode传送门,点击这里!!

    //要求原地进行反转
    //1,只用一个节点指针,进行头插
    class Solution {
        public ListNode reverseList(ListNode head) {
            if(head == null){
                return null;
            }
            ListNode cur = head.next;
            head.next = null;
            while(cur != null){
                ListNode curNext = cur.next;
                cur.next = head;
                head = cur;
                cur = curNext;
            }
            return head;
        }
    }
    
    //2,两个节点指针,原地逆序
    class Solution {
        public ListNode reverseList(ListNode head) {
            if(head == null){
                return null;
            }
            
            ListNode prev = head;
            ListNode cur = head.next;
            head.next = null;
            
            while(cur != null){
                ListNode curNext = cur.next;
                cur.next = prev;
                prev = cur;
                cur = curNext;
            }
            head = prev;
            return head;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    【图示:第一种方法】

    在这里插入图片描述


    【图示:第二种方法】

    在这里插入图片描述


    3,寻找中间节点

    具体题目:给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点

    Leetcode传送门,点击这里!!

    可能有的同学会觉得很简单,先遍历一遍求出长度,然后不就顺理成章的知道中间节点了嘛。但是,面试题的要求肯定不会让你如此,假如要求你只能遍历一遍就把问题解决,那你如何做呢?

    //这里用到了 快慢指针 的思想
    class Solution {
        public ListNode middleNode(ListNode head) {
            ListNode fast = head;
            ListNode slow = head;
            while(fast != null && fast.next != null){
                fast = fast.next.next;
                slow = slow.next;
            }
            return slow;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    【图示:】

    在这里插入图片描述


    这里解释一下快慢指针的原理,因为它是找到中间节点,我们的fast与slow指针的起点一样,路程也一样,但是fast的速度是slow的二倍,这样当我们的fast走到终止时,slow的位置就是我们要求的中间节点。


    4,链表中倒数第K个节点

    牛客传送门,点击这里!!

    //要求还是只让你遍历一遍链表
    import java.util.*;
    public class Solution {
        public ListNode FindKthToTail(ListNode head,int k) {
            if(head == null){
                return null;
            }
            if(k <= 0){//判断k的合法性
                return null;
            }
            ListNode fast = head;
            ListNode slow = head;
            
            while(k - 1 != 0){//让fast先走k-1步
                fast = fast.next;
                if(fast == null){
                    return null;//说明k过大
                }
                k--;
            }
            while(fast.next != null){//fast.next == null 就说明fast到了最后节点了,就不走了
                fast = fast.next;
                slow = slow.next;
            }
            return slow;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    这个题还是快慢指针的思想。它既然想找到我们的倒数第k个节点,我们就想让fast先走k-1步,这样的话,等到fast到最后一个节点时,slow的位置就是我们的倒数第k个节点处。这里的关键就是我们的倒数第k个节点与我们的最后一个节点之间差的就是k-1步,这也是为什么要将fast先走k-1步的原因。


    【图示:】
    在这里插入图片描述


     while(k - 1 != 0){//让fast先走k-1步
        fast = fast.next;
        if(fast == null){
            return null;//说明k过大
        }
        k--;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里还有一个点就是我们的k肯定是不能大于我们的链表长度的,但是要求是只能遍历一遍链表,所以我们没办法去求链表的长度,但是我们知道,限制k不能过大的原因就是fast走的时候可能直接变成null了,所以我们可以把这个条件转换到这里的先走k-1步中,因为假设我们的链表长度是len,那么极端情况就是求倒数第len个,那么fast最多先走len-1步,所以如果当k> size()的时候,k-1 >= size(),明显就不符合了,fast在移动的过程中就会变成null,这个时候就说明k过大了。


    5,合并两个有序链表

    具体题目:将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

    Leetcode传送门,点击这里!!

    class Solution {
        public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
            ListNode newNode = new ListNode(-1);//虚拟节点
            ListNode tmp = newNode;
            while(list1 != null && list2 != null){
                if(list1.val < list2.val){
                    tmp.next = list1;
                    tmp = list1;
                    list1 = list1.next;
                }else{
                    tmp.next = list2;
                    tmp = list2;
                    list2 = list2.next;
                }
            }
            if(list1 == null){//也包含初始条件下某一个就为null的情况
                tmp.next = list2;
            }
            if(list2 == null){
                tmp.next = list1;
            }
            return newNode.next;//然会新的头节点,原两个链表的头节点都动了
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    看到这个题呢,可能很多同学会想起合并两个有序数组,这二者之间确实有着异曲同工之妙,不一样的是这里我不需要额外的空间,因为链表我可以直接改变next域让其串起来,即使是两个链表的元素。同样是指针遍历,list1,list2两个节点指针去遍历元素,tmp记录当前所在元素的位置。


    【图示:】

    在这里插入图片描述


    6,分割链表

    具体题目:编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前。

    牛客传送门,点击这里!!

    import java.util.*;
    public class Partition {
        public ListNode partition(ListNode pHead, int x) {
            ListNode as = null;
            ListNode ae = null;
            ListNode bs = null;
            ListNode be = null;
            
            ListNode cur = pHead;
            while(cur != null){
                if(cur.val < x){
                    if(as == null){//可能是放入第一个数据
                        as = cur;
                        ae = cur;
                    }else{
                        ae.next = cur;
                        ae = ae.next;
                    }
                }else{
                    if(bs == null){
                        bs = cur;
                        be = cur;
                    }else{
                        be.next = cur;
                        be = be.next;
                    }
                }
                cur = cur.next;
            }
            
            //然后把两个段链接起来
            if(as == null){//有可能没有第一段
                return bs;//bs也有可能是null
            }
            ae.next = bs;//把两段连起来,如果第二段不存在,刚好用来把第一段结尾置为null
            if(bs != null){//如果存在第二段,那么第二段的尾巴需要手动置null一下
                be.next = null;
            }
            return as;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    【图示:】

    在这里插入图片描述


    7,链表的回文结构

    牛客传送门,点击这里!!

    import java.util.*;
    public class PalindromeList {
        public boolean chkPalindrome(ListNode head) {
            if(head == null){
                return true;//空链表算回文
            }
            ListNode fast = head;
            ListNode slow = head;
            
            //先找到中间节点
            while(fast != null && fast.next != null){
                fast = fast.next.next;
                slow = slow.next;
            }
            
            //从中间后进行链表的翻转
            ListNode cur = slow.next;
            while(cur != null){
                ListNode curNext = cur.next;
                cur.next = slow;
                slow = cur;
                cur = curNext;
            }
            
            //翻转完毕,slow现在在最后一个节点处
            while(head != slow){
                if(head.val != slow.val){
                    return false;
                }
                if(head.next == slow){
                    return true;
                }
                head = head.next;
                slow = slow.next;
            }
            
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    回文结构,按照面试的要求,肯定是不能用除链表之外的空间的,所以你只能原地判断。那想法肯定是前后遍历然后对比值,但是链表如何能够从后往前走呢?此时反转是不是就可以达到目的了,所以我们的思路就是
    1,找到中间节点 2,从中间节点往后开始反转链表 3,判断回文。


    【图示:】

    在这里插入图片描述


    8,相交链表的公共节点

    Leetcode传送门,点击这里!!

    public class Solution {
        public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
            if(headA == null || headB == null){
                return null;
            }
            ListNode fast = headA;//fast指向的是长的那个链表,现在假设是headA长
            ListNode slow = headB;
            int subLen = 0;
            int lenA = 0;
            int lenB = 0;
            while(fast != null){
                lenA++;//从headA计算长度
                fast = fast.next;
            }
            while(slow != null){
                lenB++;//从headB计算长度
                slow = slow.next;
            }
    
            subLen = lenA - lenB;
            //因为动了fast与slow,现在重新让他们指向开头,同时更新一下fast与slow的指向
            if(subLen < 0){
                fast = headB;
                slow = headA;
                subLen = lenB - lenA;
            }else{
                fast = headA;
                slow = headB;
            }
    
            while(subLen != 0){//让长的那一方的指针fast先走差值步
                fast = fast.next;
                subLen--;
            }
    
            while(fast != slow){
                fast = fast.next;
                slow = slow.next;
            }
    
            return fast;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    首先我们得弄清楚一个点,两个链表相交,是怎么相交? X 型 或者是 Y型?答案肯定是 Y型,因为我们的链表一个节点的后继肯定只有一个,所以不肯能是X型。


    【图示:】

    在这里插入图片描述


    9,判断链表中是否有环

    Leetcode传送门,点击这里!!

    public class Solution {
        public boolean hasCycle(ListNode head) {
            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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以看到,原理还是很简单,快慢指针的追及问题,因为如果有环存在,那么在快指针与慢指针的移动过程中,一定会相遇。但是,这里有一个关键问题就是,快指针与慢指针之间的速度差是多少?怎么确定?

    答案是:快指针一次走两步,慢指针一次走一步。原因如下:

    在这里插入图片描述

    快指针肯定是先进环,慢指针后进环,那么这个时候,最好的情况就是刚好slow过来就直接和fast相遇了;最差的情况就是二者之间刚好差一个环的长度。fast指针每次走两步,slow指针每次走一步,那么每移动一次,二者之间的距离就会少一步(相对于slow,fast的每次移动都多一步,所以移动一次,中间距离就会缩小一步),这个时候其实就是一个fast去追slow的问题,并且不会出现套圈的问题。所以,在slow把这圈走完之间,fast绝对可以把slow追上。


    可能有的同学会问快指针可不可以一次走3步,或者4步,或者更多?其实我们一次只走两步,这个决定的考量就是为了防止套圈的问题出现,因为快慢指针之间的速度差越大,在某种程度上,可能会更加快的追上,但同时套圈的危险性就更大了,比如:

    在这里插入图片描述

    注意,我们是两个都走完了才开始比,移动过程中的相遇是不算的,综上,就是我们选择走两步与走一步的原因。


    10,返回入环节点

    具体题目:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL。

    Leetcode传送门,点击这里!!

    public class Solution {
        public ListNode detectCycle(ListNode head) {
            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;
            }
            //上面没有return掉,就说明有环,并且fast与slow相遇了
            fast = head;
            while(fast != slow){
                fast = fast.next;
                slow = slow.next;
            }
            return fast;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    【图示:】

    在这里插入图片描述


    最后,今天的文章分享比较简单,就到这了,如果大家觉得还不错的话,还请帮忙点点赞咯,十分感谢!🥰🥰🥰
    在这里插入图片描述

  • 相关阅读:
    树莓派(四)树莓派外设开发基础篇
    骨传导耳机和气传导区别有哪些?骨传导和气传导耳机科普
    Time-Frequency Signal Analysis and Processing 笔记
    [iOS]-autoreleasePool
    【零基础学QT】第八章 文件操作,网络文件传输实验
    函数对象类,函数对象(又称仿函数)
    Python 自动化测试:数据驱动
    docker简单实战
    搜题公众号搭建
    ROS基础-ROS msg发布订阅:嵌套自定义类型 数组
  • 原文地址:https://blog.csdn.net/qq_61688804/article/details/125612459