链表常见面试题总结

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

删除某一个节点的原理就是不再有引用指向该节点,Java虚拟机会自动回收
如下链表:
链表
假设给定值为3其结果为:
删除给定值以后的链表

参考代码:

 //删除所有值为key的节点
    public void removeAllKey(int val){
        if(this.head == null){//若没有节点,直接返回
            return;
        }
        ListNode prev = this.head;
        ListNode cur = this.head.next;
        while(cur!=null){//节点不为空,进入循环
            if(cur.val == val){//找到一个与指定值相同的节点,删除
                prev.next = cur.next;
                cur = cur.next;//删除后向后移动一位
            }else{//与指定值不同,向后移动一位
                prev = cur;
                cur = cur.next;
            }
        }
        //最后判断头节点是否为该值
        if(this.head.val == val){
            this.head = this.head.next;

        }
    }

2. 反转一个单链表。

反转指的是反转节点,不是将节点的值反转
反转前:
反转前的链表
反转后:
反转后的链表
注意:单链表尾节点的next域为null

参考代码:

//反转一个单链表
    public ListNode reverseList(){
       //若没有节点或只有一个节点,直接返回
        if(head == null||head.next == null){
            return head;
        }
        ListNode cur = head;
        ListNode newHead = null;

        while(cur!=null){
            ListNode curNext = cur.next;//保留curNext,以便向后移动
            cur.next = newHead;//把本节点的next域置为newHead
            newHead = cur;//newHead节点向后移一位
            cur = curNext;//cur向后移一位
        }
        return newHead;
    }

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

采用快慢指针法,慢指针每走一步,快指针走两步。当快指针走到末尾,慢指针也就走到了中间节点

开始时快指针和慢指针都在头节点
快慢指针起始位置
最后的位置
最后位置

参考代码:

//给定一个带头节点的非空单链表,返回链表的中间节点
    public ListNode middleNode(){
        if(head == null) return head;
        ListNode fast = head;//快指针
        ListNode slow = head;//慢指针
        while(fast!=null && fast.next!=null){
            fast = fast.next.next;//走两步
            slow = slow.next;//走一步
        }
        return slow;//返回慢指针
    }

4. 输入一个链表,输出该链表中倒数第k个结点。

采用快慢指针法,快指针先走k-1步,然后快指针和慢指针同步向前走,直到快指针走到末尾

如下链表假设k为2,则其快慢指针的起始位置为:
起始位置
其最后的结果为:
结果
参考代码:

//输入一个链表,输出该链表中倒数第k个节点
    //先让快指针走k-1步,再让快指针和慢指针一起同步走
    public ListNode findKthToTail(int k){
        if(head == null) return null;//空节点
        if(k<0){//坐标不合法
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while(k-1 != 0){//处理k可能大于链表长度的情况
            if(fast.next != null){
                fast = fast.next;
                k--;
            }else{
                return null;
            }
        }
        while(fast.next != null){//快慢指针同步走
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

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

如下两个有序链表:
两个有序链表
其拼接完成以后为:
拼接完以后
参考代码:

    //将两个有序链表合并为一个新的有序链表并返回
    public ListNode mergeTowList(ListNode headA,ListNode headB){
        if(headA == null) return headB;
        if(headB == null) return headA;
        if(headA == null && headB == null) return null;

        ListNode newHead = new ListNode(-1);//申请一个头节点,傀儡节点
        ListNode tmp = newHead;//移动tmp,保留newHead位置

        while(headA != null && headB != null){//谁小先连接谁,直到其中一个达到尾结点
            if(headA.val<headB.val){//链接headA
                tmp.next = headA;
                headA = headA.next;
            }else{
                tmp.next = headB;//链接headB
                headB = headB.next;
            }
            tmp = tmp.next;//连接后再向后移一位
        }
        if(headA == null){//headA先到尾结点
            tmp.next = headB;
        }
        if(headB == null){//headB先到尾结点
            tmp.next = headA;
        }
        return newHead.next;//返回傀儡结点的下一个结点
    }

6. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 ,且保证顺序不变。

假定有如下链表:
链表
如果给定值x为6
结果

分别创建两个链表,前一个均小于x,后一个均大于x。为保证顺序不变应采用尾插法

参考代码:

//已给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前
    public ListNode partition(int x){
        ListNode cur = this.head;//用来遍历指定链表
        ListNode bs = null;//前一个链表的起始
        ListNode be = null;//前一个链表的末尾
        ListNode as = null;//后一个链表的起始
        ListNode ae = null;//后一个链表的末尾
        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){//如果所有值都大于x
            return as;
        }
        //bs!=null
        be.next = as;//be不为空,该结点一定有数据,将其与后一个链表连接起来
        if(as != null){//若as不为空,将链表的尾节点的next域置为空
            ae.next = null;
        }
        return bs;
    }

7.在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。

假设给定如下链表:
链表
其删除完重复结点以后为:
删除重复链表以后

参考代码:

    //删除一个有序链表中的重复结点,重复的结点不保留,返回链表头指针
    public ListNode deleteDuplication(){
        ListNode newHead = new ListNode(-1);//申请一个傀儡结点
        ListNode tmp = newHead;//移动tmp保证傀儡结点位置不动
        ListNode cur = this.head;
        while(cur != null){
            //cur.next!=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{//尾插法
                tmp.next = cur;
                tmp = tmp.next;
                cur = cur.next;
            }
        }
        tmp.next = null;//手动设置,防止最后一个结点是重复的
        return newHead.next;
    }

8.判断链表是否回文结构

回文结构示例:
示例
方法:

  1. 首先通过快慢指针法找到链表的中间节点
    找到中间节点

  2. 从中间节点开始对链表进行反转
    反转以后

  3. head和slow两个指针分别从头和尾向中间匹配,直到两个指针对撞,一旦遇到不一致直接返回false,否则返回true
    在这里插入图片描述
    注意:区分奇数和偶数的情况,偶数匹配时多比较一步

参考代码:

    //检查链表的回文结构,例如:12321
    public boolean chkPalindrome() {
        if (this.head == null) {
            //一个结点也没有
            return true;
        }
        if (this.head.next == null) {
            //只有一个结点
            return true;
        }
        //1找中间节点
        ListNode slow = this.head;
        ListNode fast = this.head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //slow 指向的节点就是中间节点
        //2,进行翻转
        ListNode cur = slow.next;
        while (cur != null) {
            ListNode curNext = cur.next;//保留cur.next
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }
        //反转完毕 slow指的地方就是最后一个节点
        while (slow != head) {
            if (slow.val != head.val) {
                return false;
            }
            if (head.next == slow) {//偶数情况
                return true;
            }
            head = head.next;
            slow = slow.next;
        }
        return true;
    }

9.输入两个链表,找出它们第一个公共节点

相交的链表链表具有Y型和X型

X型和Y型相交链表

如下一个具有公共节点的链表:
具有交点的链表
方法:

  1. 首先分别得到两个链表的长度,计算其差值x
  2. 让长的链表先走x步,然后让两个链表边匹配值边走,直到末尾

参考代码:

 //输入两个链表,找到他们第一个公共节点
    public static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null) {
            return null;
        }
        if(headB == null) {
            return null;
        }

        int lenA = 0;
        int lenB = 0;
        //pl:代表长的链表的长度 ps :代表短的链表的长度
        ListNode pl = headA;
        ListNode ps = headB;

        while(pl != null) {
            lenA++;
            pl = pl.next;
        }
        while(ps != null) {
            lenB++;
            ps = ps.next;
        }
        //求完长度pl和ps均已为null,需要返回头节点
        pl = headA;
        ps = headB;
        /*
        lenA 和 lenB的结果:
        lenA > lenB
        lenA == lenB
        lenA < lenB
         */
        int len = lenA-lenB;//两个链表的差值
        // < 0   == 0   > 0
        if(len < 0) {
            pl = headB;
            ps = headA;
            len = lenB-lenA;
        }
        //结果:pl永远指向的是长的链表   ps永远指向短的链表   len永远是一个正数
        while (len != 0) {
            pl = pl.next;
            len--;
        }
        while (pl != ps) {//判定两个链表是否有交点
            pl = pl.next;
            ps = ps.next;
        }
        //可以不写,假定没有相交,那也是null
        if(pl == null ) {
            return null;
        }
        return pl;
    }

10.给定一个链表判断链表是否有环

带环的链表是这样:
环
具体形式链表:
带环的链表
方法:采用快慢指针法,慢指针每走一步快指针走两步,若链表有环两支针终会相遇

参考代码:

    //判断链表是否有环
    public boolean hasCycle(){
        ListNode fast = this.head;
        ListNode slow = this.head;

        while(fast!=null && fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast){
                break;
            }
        }
        if(fast == null || fast.next == null){
            return false;
        }
        return true;
    }

11.给定一个链表,返回链表开始入环的第一个节点。如果链表无环,则返回null

采用快慢指针法,快指针的速度为慢指针的两倍

前提假设:
在这里插入图片描述
相遇公式的推导:

公式推导
由于每圈都是回到原点最后等价于L=X

参考代码:

    //给定一个链表,返回链表开始入环的第一个节点,若链表无环,则返回null
    public ListNode detectCycle(){
        ListNode fast = this.head;
        ListNode slow = this.head;

        while(fast!=null && fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast){//第一次相遇
                break;
            }
        }
        if(fast == null || fast.next == null){//没环
            return null;
        }
        //slow和fast是相遇的
        slow = this.head;//slow从起始位置开始走,fast从相遇点开始走
        while(fast!=slow){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}
  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
HashMap的面试题主要涉及以下几个方面: 1. HashMap的底层数据结构是什么? HashMap的底层数据结构是数组。当进行put()操作时,会进行hash计算,确定对象在数组中的位置。如果多个对象的值在同一个数组位置上,就会出现hash冲突,此时会使用链表来解决冲突。 2. JDK 1.8为什么引入了红黑树? JDK 1.8引入红黑树是为了解决链表过长导致的性能问题。当链表长度超过一定阈值(默认为8),链表会转换为红黑树来提高查找效率。 3. HashMap的内部类Node<K,V>是什么作用? HashMap的内部类Node<K,V>实现了Entry接口,用于存储键值对的数据。每个Node对象表示一个映射关系,包含了键和值。 以上是关于HashMap的一些常见面试题,希望对你有帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [史上最全Hashmap面试总结,51道附带答案,持续更新中...](https://blog.csdn.net/androidstarjack/article/details/124507171)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【硬核】HashMap最全面试题(附答案)](https://blog.csdn.net/weixin_43689480/article/details/118752906)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值