【JAVA数据结构】经典链表面试题(附超详细代码注释和图解)

【JAVA数据结构】经典链表面试题(附超详细代码注释和图解),第1张

【JAVA数据结构】经典链表面试题(附超详细代码注释和图解)

文章目录
  • 1、反转单链表
  • 2、给定一个带有头结点 head 的非空单链表,返回链表的中间结点
  • 3、输入一个链表,输出该链表中倒数第k个结点
  • 4、将两个有序链表合并为一个新的有序链表并返回
  • 5、分割链表
  • 6、删除该链表中重复的结点,重复的结点不保留
  • 7、 链表的回文结构
  • 8、输入两个链表,找出它们的第一个公共结点。
  • 9、给定一个链表,判断链表中是否有环。
  • 10、给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

1、反转单链表

题目介绍:

具体效果图如下:

思路分析:

1、首先设置两个指针,cur指向链表的头结点,prev指向空(默认为null)。
2、为避免cur.next为空,所以引用curNext来暂时保存一下cur的后继节点
3、让cur的后继节点指向prev。
4、prev指针向后移,即prev指向cur。
5、cur指针也向后移,指向它的下一个节点,即指向curNext节点。
6、循环执行2-5步,直到cur遍历完整个单链表为空为止。

代码如下:

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur=head;//引用cur来代替头结点
        ListNode prev=null;//引用变量prev表示当前节点的前一个节点
        //判断头结点是否为空,若为null,则返回空
        if(head==null){
            return null;
        }
        //如果头结点不为空,则进入循环
        while(cur!=null){
        //先引用curNext变量保存当前节点中的next
               ListNode curNext=cur.next;
               cur.next=prev;//把当前节点中的next指向前一个节点
               prev=cur;//前一个节点指向当前节点
               cur=curNext;//当前节点指向下一个节点
        }
        //单链表遍历完后,跳出循环
        return prev;//此时prev变成了头结点,返回
    }
}
2、给定一个带有头结点 head 的非空单链表,返回链表的中间结点

题目介绍:

思路分析:
该题我们可以使用双指针(快慢指针)的方法 ,快指针速度是慢指针的2倍,那么它的路程也是慢指针的2倍,所以快指针到结尾了,慢指针也就在中间位置了。
1、首先设置两个指针,一个快指针,一个慢指针。
2、快指针每次走两步,慢指针每次走一步。
3、当快指针走到最后一个节点时,慢指针正好走到链表的中间节点。

代码如下:

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode fast=head;//快指针刚开始指向头结点
        ListNode slow=head;//慢指针刚开始也指向头结点
        //判断头结点是否为空
        if(head==null){
            return null;
        }
        //由于在偶数链表情况下,有两个中间节点,要返回第二个节点,所以多设置了个
        //fast!=null的条件
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;//快指针一次走两步
            slow=slow.next;//慢指针一次走一步
        }
        return slow;//返回慢指针
    }
}
3、输入一个链表,输出该链表中倒数第k个结点

题目介绍:

思路分析:

1、首先让快指针fast和慢指针slow开始时都指向头结点;
2、让快指针fast走k-1步;
3、让快指针和慢指针同时开始一步一步走,直到快指针走到最后一个节点为止,此时慢指针正好指向倒数第k个节点。

代码如下:

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast=head;//在刚开始时使快指针指向头结点
        ListNode slow=head;//让慢指针也指向头结点
        //判断倒数第k个节点是否合法,头结点是否为空
        if(k<=0||head==null){
            return null;
        }
        //快指针先走k-1步
        while(k-1!=0){
            fast=fast.next;
            k--;
        }
        //快指针和慢指针同时向后一步一步走,直到快指针走到尾结点为止
        while(fast.next!=null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;//此时慢指针正好指向倒数第k个节点,返回即可
    }
}
4、将两个有序链表合并为一个新的有序链表并返回

题目介绍:

思路分析:

1、首先设置个傀儡节点;
2、然后比较两个链表的每个节点,依次存入傀儡节点后的链表中;
3、如果有一个链表已经遍历完,则将另一个链表中的其它节点连接到合并链表的尾部;
4、最后返回傀儡节点的下一个节点,这才是合并后的链表的头结点。
代码如下:

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode newHead=new ListNode(0);//创建一个新节点当傀儡节点
        ListNode tmp=newHead;//引用tmp指向傀儡节点
        //进入循环,依次比较节点
        while(l1!=null&&l2!=null){
            if(l1.vall1或者l2==l1时
            else{
                tmp.next=l2;//傀儡节点的后继指向了l2
                l2=l2.next;//l2往后走了一步,指向下一个节点
                tmp=tmp.next;//傀儡节点也往后走了一步,便于指向下一个节点
            }
        }
        if(l1==null){
            tmp.next=l2;//当l1链表遍历完后,tmp指向l2中其它的节点
        }
       else{
            tmp.next=l1;//当l2链表遍历完后,tmp指向l1中其它的节点
        }
        return newHead.next;//返回傀儡节点后一个节点,相当于是合并链表的头结点
    }
}
5、分割链表

题目介绍:

思路分析:
1、首先创建4个引用,as和ae表示第一个段(用来存放比特定值小的数),bs和be表示第二个段(用来存放比特定值大的数);
2、然后依次遍历原链表,不过需要注意的是,遍历结束后,我们要将第二段尾指针的next置空。因为当前节点引用的是原链表的节点,而其指针可能指向一个小于特定值的节点,所以我们需要切断这个引用。
3、最后让前一段尾结点的指针指向后一段的头结点即可。
代码如下:

class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode as = null;//表示前一段的头结点
        ListNode ae = null;//表示前一段的尾结点
        ListNode bs = null;//表示后一段的头结点
        ListNode be = null;//表示后一段的尾结点
        ListNode cur = head;//引用cur指向头结点
        while (cur != null) {
            if (cur.val < x) {//当原链表头结点的值小于特定值x时
                if (as == null) {//当前一段的头结点为空时
                    as = cur;//前一段的头结点和尾结点都为原链表的头结点
                    ae = cur;
                } else {//当前一段的头结点不为空时,已经有插入的元素时
                    ae.next = cur;//尾结点的后继指向原链表的cur节点
                    ae = ae.next;//尾结点往后移,指向下一个节点
                }
            } else {//当原链表头结点的值大于特定值x时
                if (bs == null) {//当后一段的头结点为空时
                    bs = cur;//后一段的头结点和尾结点都为原链表的头结点
                    be = cur;
                } else {//当后一段的头结点不为空时,已经有插入的元素时
                    be.next = cur;//尾结点的后继指向原链表的cur节点
                    be = be.next;//尾结点往后移,指向下一个节点
                }
            }
            cur = cur.next;//原链表的cur节点指向下一个节点
        }
        //如果没有比特定值小的元素,则前一段为空,直接返回后一段
        if (as == null) {
            return bs;
        }
        //若前一段不为空,则前一段的尾结点的后继指向后一段的头结点
        ae.next = bs;
        //然后继续遍历下去,如果遍历到最后一个节点,则使最后一个节点的next为null
        if (bs != null)
            be.next = null;
        return as;//返回前一段的头结点
    }
}
6、删除该链表中重复的结点,重复的结点不保留

题目介绍:

思路分析:

1、引用变量cur代替头结点对原链表进行遍历;
2、创建个傀儡节点当作新链表,并引用变量tmp代替傀儡节点;
3、在原链表中,让当前节点的值与下一个节点的值进行比较,如果不相等则引入新链表,如果相等,则使cur指向不与这个值相等的那个节点;
4、最后返回新头结点的下一个节点(真正的头结点)。
代码如下:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode cur = head;//引用cur来代替头结点遍历原链表
        ListNode newHead = new ListNode(0);//创建一个傀儡节点
        ListNode tmp = newHead;//引用变量tmp来替代傀儡节点
        while (cur != null) {//遍历整个链表,cur节点为空时结束
        //在当前节点的下一个节点不为空的情况下(避免空指针异常)判断当前节
        //点和下一个节点是否相同
            if (cur.next != null && cur.val == cur.next.val) {
            //由于要考虑到如果有两个以上的重复节点该如何处理,所以我们可以利用
            //while循环来实现
                while (cur.next != null && cur.val == cur.next.val) {
                    cur = cur.next;
                }
                cur = cur.next;//重复节点都找到后,我们要往后面多走一步,因为要求不保留重复节点,所以我们要跳到重复节点的下一个节点
            } else {//在不满足条件得情况下
                tmp.next = cur;//傀儡节点的后继指向原链表的cur结点
                tmp = tmp.next;//傀儡节点向后走一步,便于指向下一个节点
                cur = cur.next;//cur节点也向后走一步,便于与下一个节点的值进行比较
            }
        }
        tmp.next = null;//防止最后一个节点也是重复的,所以tmp的后继置为null
        return newHead.next;//返回傀儡节点的下一个节点,那才是新链表真正的头结点
    }
}
7、 链表的回文结构

题目介绍:

思路分析:
1、利用快慢指针找到中间节点;
2、将中间节点后面的节点进行反转;
3、判断头结点的值和尾结点的值是否相同,若不相同则返回false,若相同,则头结点从前往后走,尾结点从后往前走,继续比较,直到两个节点相遇,返回true;
4、我们还要考虑一下偶数链表的情况。
代码如下:

class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode fast=head;//开始时使快指针指向头结点
        ListNode slow=head;//慢指针也指向头结点
        //判断头结点是否为空
        if(head==null){
            return true;
        }
        //寻找中间节点
        while(fast!=null&&fast.next!=null){//若想知道详细思想请看文章第2题
            fast=fast.next.next;//快指针走两步
            slow=slow.next;//慢指针走一步
        }
        //把中间节点后的节点反转
        ListNode cur=slow.next;
        while(cur!=null){//若想知道详细思想请看文章第1题
            ListNode curNext=cur.next;
            cur.next=slow;
            slow=cur;
            cur=curNext;
        }
        //判断是否是回文链表
        while(head!=slow){//此时slow指向最后一个节点,让head从前往后走,slow从后往前走,直到它们两个相遇
            if(head.val!=slow.val){//判断第一个节点的值和最后一个节点的值是否相等
                return false;//不相等返回false
            }
            if(head.next==slow){//此时考虑的是偶数链表的情况下,如果当前节点与下个节点相等的话直接返回true
                return true;
            }
            head=head.next;//head指向下一个节点
            slow=slow.next;//slow向前走,指向前一个节点
        }
        return true;//跳出while循环说明链表是回文结构,返回true
    }
}
8、输入两个链表,找出它们的第一个公共结点。

题目介绍:

思路分析:
1、引用变量pl和ps,让pl永远指向最长的那个链表,ps永远指向那个最短的链表;
2、计算两个链表的长度并求出两个链表的差值,让那个最长的那个链表先走差值步;
3、遍历两个链表,直到两个链表节点相等,并返回那个节点。
代码如下:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    //无论链表A为空还是链表B为空,都返回null
        if(headA==null||headB==null){
            return null;
        }
        ListNode pl=headA;//pl永远指向最长的那个链表
        ListNode ps=headB;//ps永远指向最短的那个链表
        int lenA=0,lenB=0;
        //求链表A的长度
        while(pl!=null){
            lenA++;
            pl=pl.next;//pl指向下一个节点
        }
        pl=headA;//这里是因为在求链表A的长度时遍历了链表,此时pl指向了null,所以要让pl重新指向头结点代码才能继续往下进行
        //求链表B的长度
        while(ps!=null){
            lenB++;
            ps=ps.next;//ps指向下一个节点
        }
        ps=headB;//和A的原因相同
        //判断那个链表长度长
        int len=0;
        len=lenA-lenB;//求差值
        if(len<0){//长度小于0说明pl指向的不是最长的那个链表
            pl=headB;//让pl指向链表B
            ps=headA;//ps指向链表A
            len=lenB-lenA;//再次求差值
        }
        //最长的链表先走差值步
        while(len!=0){
            pl=pl.next;
            len--;
        }
        //遍历两个链表,直到两个链表节点相等为止
        while(pl!=ps){
            pl=pl.next;//pl指向下一个节点
            ps=ps.next;//ps指向下一个节点
        }
        return pl;//循环结束后说明找到了那个相交的节点,返回即可
    }
}
9、给定一个链表,判断链表中是否有环。

题目介绍:

思路分析:
判断链表是否有环实际上就是一个追击问题,因此我们可以采用双指针的方法,设置一个快指针一个慢指针,快指针每次走两步,慢指针每次走一步。如果链表有环的话,那么快指针和慢指针终究会在环里面相遇。
1、设置一个快指针fast,一个慢指针slow,两个指针刚开始都指向头结点;
2、快指针每次走两步,慢指针每次走一步;
3、如果快指针和慢指针相等了,说明该链表有环返回true,否则该链表无环,返回false。
代码如下:

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null)return false;//如果头结点为空则直接返回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;//返回true
        }
    }
        return false;//跳出了循环说明该链表中没有环,则返回false
    }
}
10、给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

题目介绍:

思路分析:

由于任意时刻,fast 指针走过的距离都为slow 指针的 2 倍。所以我们可以得到X=(N-1)*C+Y这样的关系式,从中可以发现:从相遇点到入环点的距离加上 N-1圈的环长,正好等于从链表起始点到入环点的距离。
因此,当slow 与 fast 相遇的时候,我们可以使用slow或者fast(本文是让fast指向)指向链表头部;然后,它和 slow 每次向后走一步。最终,它们相遇的节点即是它们的入环点。
1、首先利用快慢指针找到相遇点;
2、找到相遇点之后,让fast指向链表的头节点,然后让头节点和slow同时移动,直到它们相遇;
3、最终,它们相遇的节点即是它们的入环点,返回即可。
代码如下:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head==null)return null;//判断头结点是否为空,若为空则返回null
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow){//利用快慢指针找到它们的相遇点
                fast=head;//此时可以让快指针指向头结点(让慢指针也可以)
                while(fast!=slow){//头结点和慢指针同时向后一步一步走,直到它们相遇
                    fast=fast.next;
                    slow=slow.next;
                }
                return fast;//它们相遇的节点就是链表中环的入口点,返回即可
            }
        }
        return null;//若在链表中找不到环则直接返回null
    }
    }

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5574986.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存