链表(LinkedList)面试题

1.1 ​​​​​​203. 移除链表元素 - 力扣(LeetCode)

分析:题目的要求是移除链表中值为val的所有元素,因此这道题需要使用循环解决问题,删除过程需要记录前一个结点的信息,所以需要使用双坐标解决问题。

解题思路:设置prev和cur两个坐标,prev坐标从head开始,cur坐标从head.next开始,cur用来找到要删除的元素(遍历数组),prev用来记录被删元素的前一个元素,最后需要单独删除头结点(因为cur是从head.next开始删除)。下面是删除过程的图解:

完整代码:

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){
                prev.next = cur.next;
                cur = cur.next;
            }else{
                prev = prev.next;
                cur = cur.next;
            }
        }
        if(head.val == val){
            head = head.next;
        }
        return head;
    }
}

1.2 206. 反转链表 - 力扣(LeetCode)

分析:本题需要原地反转一个单链表,并返回头节点。例如:12345 反转后为 54321。

解题思路:采用头插法,从第二个元素开始不断头插, 头插之前需要判断链表是否为空和链表是否只有一个元素,防止空指针异常。下图是头插法的过程和代码:

完整代码:

class Solution {
      public ListNode reverseList(ListNode head) {
        if(head==null){
            return null;
        }
        if(head.next==null){
            return head;
        }
        //cur从第二个节点开始
        ListNode cur = head.next;
        //将head的后置节点置空
        head.next = null;
        while(cur != null){
            //记录当前需要反转的节点的下一个节点,防止反转的过程丢失
            ListNode curNext = cur.next;
            //绑定头插节点的后一个结点
            cur.next = head;
            //把头给到当前结点
            head = cur;
            cur = curNext;
        }
        return head;
    }
}

1.3 876. 链表的中间结点 - 力扣(LeetCode)

分析:本题需要找到链表的中间结点并返回中间结点 ,如果链表结点个数为偶数有两个中间结点,返回第二个中间结点。

解题思路:使用快慢指针,快指针一次走两步,慢指针一次走一步,当快指针走到链表的末端,慢指针就正好走到链表的中间位置。图解如下:

这里需要注意需要分奇偶结点数讨论:奇数时走到fast.next == null的时候,偶数时走到fast == null的时候并且要将偶数的判断条件放在前面,如果将判断奇数的条件放在前面,那么在判断偶数结点的时候会发生空指针异常。

完整代码:

class Solution {
    public ListNode middleNode(ListNode head) {
      ListNode fast = head;
            ListNode slow = head;
            //原理:当fast的速度是slow的两倍时,fast走到了终点,slow就走到了中点
            while(fast != null && fast.next != null){
                fast = fast.next.next;
                slow = slow.next;
            }
            return slow;
    }
}

1.4 面试题 02.02. 返回倒数第 k 个节点 - 力扣(LeetCode)

分析:本题需要找到一个单向链表的倒数第k个结点并返回它的值。

解题思路:使用快慢指针,先让快指针走k-1步,此时,快慢指针正好差了k个结点,再让快慢指针一起走,当快指针遍历完链表时,慢指针正好走到倒数第k个结点。图解如下:完整代码:

class Solution {
    public int kthToLast(ListNode head, int k) {
     //这里最好能够判断k的合法性
        //1、fast先走k-1步
        int count = 0;
        ListNode fast = head;
        while(count < k-1){
            fast = fast.next;
            count++;
        }
        //slow和fast一起走
        ListNode slow = head;
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow.val;
    }
}

1.5 21. 合并两个有序链表 - 力扣(LeetCode)

分析:本题需要将两个升序链表合并为一个升序链表 ,返回新升序链表的头节点,这道题与我们上一篇讲过的合并两个数组大同小异,不同的是数组是原地返回且通过下标来访问元素,而本篇需要创建一个新链表,通过next来进行元素的比较。

解题思路:先定义一个新链表的头节点,再设计一个tmp等于这个头节点,它用来连接这个链表的元素,再通过两个链表的头节点对里面的元素进行比较,小的在前,大的在后,当有一个链表走完则退出循环,将剩余链表的元素塞在新链表后面。图解如下:

完整代码:

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
  
        ListNode nHead = new ListNode();//不具备有效数据
        ListNode tmp = nHead;
        //两个链表有一个为null则退出循环
        while(list1 != null && list2 != null){
            if(list1.val > list2.val){
                //插入list2中的元素
                tmp.next = list2;
                list2 = list2.next;
            }
            else{
                //插入list1中的元素
                tmp.next = list1;
                list1 = list1.next;
            }
            tmp = tmp.next;
        }
        //list1没走完,将当前list1的结点和tmp的后置相连
        if(list1 != null){
            tmp.next = list1;
        }
        //list2没走完,将当前list2的结点和tmp的后置相连
        if(list2 != null){
            tmp.next = list2;
        }

        return nHead.next;
    }
}   

1.6 链表分割_牛客题霸_牛客网

分析 :本题目需要新建一个链表将链表中val值小于x的结点往前放,将val值大于x的节点往后放,并且不改变原来的顺序。

解题思路:将新的链表分成两段,设置bs(前面链表的头)、be(前面链表的尾)、as(后面链表的头)、ae(后面链表的尾),再设置一个cur值遍历链表,将值小于x的往前面链表中放,将大于x的值往后面链表中放,需要注意如果是前面或后面链表的第一个节点需要让头和尾一起指向它。最后如果前面链表为空则直接返回as(后面链表的头),再把前面链表和后面链表连起来,最最后如果后面链表有节点还需要将ae(后面链表的尾)的next置空。

完整代码:

public class Partition {
    public ListNode partition(ListNode pHead, int x) {
    //1、保证原有顺序不变,建议采用尾插法
        ListNode bs = null;
        ListNode be = null;
        ListNode as = null;
        ListNode ae = null;
        ListNode cur = pHead;
        //遍历完整个链表
        while(cur != null){
            if(cur.val < x){
                if(bs == null){
                    bs = cur;
                    be = cur;
                }else{
                    be.next = cur;
                    be = be.next;
                }
            }else{
                if(as == null){
                    as = cur;
                    ae = cur;
                }else{
                    ae.next = cur;
                    ae = ae.next;
                }
            }
            cur = cur.next;
        }
        //第一个段   没有数据
        if(bs == null){
            return as;
        }
        be.next = as;
        //防止链表循环
        if(as != null){
            ae.next = null;
        }
        return bs;
    }
}

1.7 链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

分析:本题需要判断一个单链表是否回文,回文返回true,不回文返回false。那么,这时候我们会想:这怎么判断呢?单链表只能一直往后遍历,无法往前遍历。其实只需要将中间节点后面的节点反转就好了。

解题思路:1、先找到中间节点。2、将中间节点后面的节点进行反转。3、从前、从后开始比较,判断条件:1、没有相遇 2、值一样,一旦不一样返回false。 注意:1、slow一定在最后一个元素那里,fast不一定,因此是slow与A(头)比较;2、如果链表的结点数为单数,那么这两个节点永远不会相遇,因此需要判断,如果A.next == slow的话,也是回文链表,返回true。

1、找到链表中间节点

ListNode fast = A;
        ListNode slow = A;
        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(A != slow){
            if(A.val != slow.val){
                return false;
            }
            //解决偶数情况
            if(A.next == slow){
                return true;
            }
            A = A.next;
            slow = slow.next;
        }

若节点数为偶数则A和slow永远不会相遇,出现出不了循环这种情况。因此需要多添加一条判断语句 。

1.8 160. 相交链表 - 力扣(LeetCode)

分析 :本题需要看一个链表是否相交,如果相交,返回相交的节点,如果不相交返回null。首先,我们要理解什么是链表相交,链表相交说明两个链表有一个公共点,且两个链表在公共点后面的节点是一样的。也就是说链表的相交一定是Y型的。

解题思路:1、定义cur1和cur2分别求两个链表的长度。2、求出链表的长度差值,让长的cur先走差值步。3、让cur1和cur2一起走直到相遇,相遇点就是返回值。图解如下:

完整代码:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        
       //1、相交是Y字型
        //2、两个链表长度不同主要体现在相交之前
        //3、可以先让最长的链表先走它们的差值步
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        int count1 = 0;
        int count2 = 0;
        //分别求两个链表的长度
        while(cur1 != null){
            count1++;
            cur1 = cur1.next;
        }
        while(cur2 != null){
            count2++;
            cur2 = cur2.next;
        }
        //求出链表的长度差值,让长的先走差值步
        cur1 = headA;
        cur2 = headB;
        int sum = 0;
        if(count1 > count2){
            sum = count1 - count2;
            while(sum > 0){
                cur1 = cur1.next;
                sum--;
            }
        }else{
            sum = count2 - count1;
            while(sum > 0){
                cur2 = cur2.next;
                sum--;
            }
        }
        //一起走直到相遇
        while(cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }
}

1.9 141. 环形链表 - 力扣(LeetCode)

分析:本题需要判断链表是否有环,有环返回true,没有环返回false。那么什么是链表有环呢?如下图就是一个环形链表:

解题思路 :设置fast和slow两个坐标,fast走两步,slow走一步,如果fast和slow相遇,则说明有环,返回true;没有相遇,返回false。(注意循环条件应为:fast != null && fast.next != null,而不是fast == slow,因为需要防止链表没有环,出不了循环的情况)

那么问题来了:可不可以让快指针一次走3步,慢指针一次走1步呢?

不可以,假设环如下图所示,则slow和fast永远不会相遇:

那可不可以让快指针走n步呢?

没有必要,如果让快指针走n步,相遇所花的时间会更长,还可能出现上面这种永远不相遇的情况。

完整代码 :

public class Solution {
    public boolean hasCycle(ListNode head) {
        //追击问题
        if(head==null)return false;
        ListNode fast = head;
        ListNode slow = head;
        //注意循环条件不是快慢指针相等        防止链表本身没有环
        while(fast != null && fast.next != null){
            //先让slow和fast这两个坐标走再判断
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }
}

1.10 142. 环形链表 II - 力扣(LeetCode)

分析:本题需要找到链表入环的第一个节点,返回链表入环的第一个节点。

解题思路:本题与上一题有着密切的联系,完成本题也需要先判断链表链表是否有环,与上题不同的是,本题还需要找到入环的第一个节点。那么要怎么找呢?**先说结论:当fast和slow在相遇点相遇之后,将其中一个引用(fast)再拉到起始点的位置,然后fast和slow同时再一步一步的走,下次两个引用重合的位置,就是入口点的位置。**下面是证明这个结论的过程:

完整代码:

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;
        }
        //结论
        //让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针
        //都是每次均走一步,最终肯定会在入口点的位置相遇。
        fast = head;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
}

1.11 删除链表中重复的结点_牛客题霸_牛客网 (nowcoder.com)

分析:本题需要生成一个新链表,新链表中没有原链表中重复的节点。

解题思路:再原链表中生成一个cur坐标遍历链表,当cur.next != null(这个要放在前面防止空指针异常) && cur.val = cur.next.val时,就让cur = cur.next,但是我们不知道一共有多少个重复的元素,因此在判断需要使用循环,循环后还应让cur多走一步,此时的元素才是下一个需要判断的元素。如果不是重复的元素则直接让他插入新链表中即可,最后还需要将新链表的最后一个节点置空,防止原链表的最后一个节点是重复节点的情况。图解如下:

cur需要往后再走一步

若将上图中的56改为23那么就会出现问题:新链表会打印出原链表。

因此我们需要把新链表的最后一个元素的next置空,th.next = null。

完整代码:

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
          ListNode cur = pHead;
        ListNode nHead = new ListNode(0);
        ListNode th = nHead;
        //遍历链表的每个节点
        while(cur != null){
            if(cur.next != null && cur.val == cur.next.val){
                //一直让cur走到不重复的节点     然后把这个节点添加到链表中
                while(cur.next != null && cur.val == cur.next.val){
                    cur = cur.next;
                }
                cur = cur.next;
            }else{
                th.next = cur;
                th = th.next;
                cur = cur.next;
            }
        }
        th.next = null;
        return nHead.next;
    }
}
相关推荐
Ypuyu2 小时前
[H数据结构] lc1206. 设计跳表(模拟+数据结构+跳表实现+优秀博文)
数据结构
烟雨迷3 小时前
八大排序算法(C语言实现)
c语言·数据结构·学习·算法·排序算法
tt5555555555553 小时前
每日一题——打家劫舍
c语言·数据结构·算法·leetcode
shinelord明5 小时前
【再谈设计模式】访问者模式~操作对象结构的新视角
开发语言·数据结构·算法·设计模式·软件工程
程序员南飞5 小时前
算法-数据结构-图-邻接表构建
java·数据结构·算法·职场和发展
噗噗bug7 小时前
数据结构 1-2 线性表的链式存储-链表
数据结构·链表
我想吃余7 小时前
【初探数据结构】时间复杂度和空间复杂度
数据结构·算法
a_j587 小时前
算法与数据结构(环形链表II)
数据结构·算法·链表
愚戏师7 小时前
从零到一学习c++(基础篇--筑基期十一-类)
开发语言·数据结构·c++·学习·算法