Java链表常见面试题

我们首先得创建一个单链表

class ListNode {
    public int val;
    public ListNode next;
    public ListNode(){
    }
    public ListNode(int val){
        this.val=val;
    }
}

我们直接写的是方法,就不具体实现链表了

反转单链表

题目链接
思路

①:从后到前,指针指向前一个节点
②:头插法

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode newHead=null;
        ListNode cur=head;
        ListNode prev=null;
        while(cur!=null){
        //保存cur.next
            ListNode curNext=cur.next;
            if(curNext==null){
                newHead=cur;
            }
            cur.next=prev;
            prev=cur;
            cur=curNext;
        }
        return newHead;
    }
}

在这里插入图片描述

头插法

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev=null;
        while (head!=null) {
            ListNode cur=head;
            head=head.next;
            cur.next=prev;
            prev=cur;
        }
        return prev;
    }
}

在这里插入图片描述

链表的中间节点

题目链接
思路

快慢指针,快的一次走两步,慢的一次走一步,循环结束,慢指针就是中间节点

class Solution {
    public ListNode middleNode(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;
        }
        return slow;
    }
}

在这里插入图片描述

输出链表中倒数第K个节点

题目链接

思路

①:求链表长度len,走len-k步
②:快慢指针:先让fast走k步,循环结束,slow就是倒数第k个节点

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode cur=head;
        int len=getLength(head);
        //走(len-k)步
        for(int i=0;i<(len-k);i++){
            cur=cur.next;
        }
        return cur;
    }
    //求链表长度
    public int getLength(ListNode head){
        int count=0;
        ListNode cur=head;
        while(cur!=null){
            cur=cur.next;
            count++;
        }
        return count;
    }
}

在这里插入图片描述

快慢指针法

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast=head;
        ListNode slow=head;
        //先让fast走k步
        for(int i=0;i<k;i++){
            fast=fast.next;
        }
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }
        
}

在这里插入图片描述

替换法删除链表中的某一节点

题目链接

思路

因为传入函数的唯一参数为 要被删除的节点,所以我们直接和下一个节点交换

class Solution {
    public void deleteNode(ListNode node) {
        node.val=node.next.val;//换值
        node.next=node.next.next;//换址
    }
}

在这里插入图片描述

删除链表中重复的节点(不保留)

题目链接

思路

讲解全在代码中了

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null)return null;
        //傀儡节点,新链表
        ListNode newHead=new ListNode(-1);
        ListNode tmp=newHead;
        ListNode cur=head;
        while(cur!=null){
            //判断条件:值相等,还不能越界
            if(cur.next!=null && cur.val==cur.next.val){
                //可能有多个重复的节点
                //链表结尾有连续相同的,可能会空指针
                while(cur.next!=null&& cur.val==cur.next.val){
                    cur=cur.next;
                }
                //多走一步,走出重复数字的区域
                cur=cur.next;
            }else{
                //把不重复的节点接到傀儡节点后面
                tmp.next=cur;
                cur=cur.next;
                tmp=tmp.next;
            }
        }
        //新链表结尾置空
        tmp.next=null;
        return newHead.next;
    }
}

在这里插入图片描述

删除排序链表中的重复节点

题目链接

讲解全在代码中了

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode cur=head;
        while(cur!=null&&cur.next!=null){
            //遇见相邻两个值相等的,就跳过这个节点,直接接上下一个节点
            if(cur.val==cur.next.val){
                cur.next=cur.next.next;
            }else{
                //不相等就继续遍历
                cur=cur.next;
            }
        }
        return head;
    }
}

在这里插入图片描述

删除第一次出现值为key的节点

思路

找到要删除节点的前驱

public ListNode searchPrev(int key){
        ListNode cur=this.head;
        while(cur.next!=null){
            if(cur.next.val==key){//cur是前驱,cur.next是要删除节点(下一个节点),cur.next.val是要删除节点的值
                return cur;
            }
            cur=cur.next;//如果不等于,还要继续往后走
        }
        return null;//没找到返回null
    }
    //删除第一次出现关键字为key的节点
    public void remove(int key){
        //如果没节点
        if(this.head==null){
            return;
        }
        //如果是头结点
        if(this.head.val==key){
            this.head=this.head.next;
        }
        //正常删
        ListNode prev=searchPrev(key);
        if(searchPrev(key)==null){
            System.out.println("没有key的前驱!");
        }else{
            ListNode del=prev.next;
            prev.next=del.next;
        }

删除所有值为key的节点

题目链接
思路

利用前驱跳过要删除的节点

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head==null)return null;
        ListNode prev=head;
        ListNode cur=prev.next;
        //从第二个点开始判断,有前驱好判断
        while(cur!=null){
            if(cur.val==val){
                prev.next=cur.next;
                cur=cur.next;
            }else{
                prev=cur;
                cur=cur.next;
            }
        }
        //再判断头结点
        if(head.val==val){
            head=head.next;
        }
        return head;
    }
}

在这里插入图片描述

合并两个有序链表

题目链接
思路

定义一个傀儡节点,相当于一个新链表
按照升序,把节点接到新链表中
注意:如果链表一长一短,在最后判断一下,把长链表的剩下部分直接接到新链表后面就好了

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1==null)return l2;
        if(l2==null)return l1;
        //傀儡节点
        ListNode newHead=new ListNode(-1);
        ListNode cur=newHead;
        while(l1!=null && l2!=null){
            if(l1.val>l2.val){
                cur.next=l2;
                cur=cur.next;
                l2=l2.next;
            }else{
                cur.next=l1;
                cur=cur.next;
                l1=l1.next;
            }
        }
        if(l1==null){
            cur.next=l2;
        
        }else{
            cur.next=l1;
        }
        return newHead.next;
    }
}

结果 😃
在这里插入图片描述

合并K个升序链表

题目链接

思路

理解了上面的合并两个有序链表题的话,本题迎刃而解

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode res=null;
        for(int i=0;i<lists.length;i++){
            res=merge(res,lists[i]);
        }
        return res;
    }
    public ListNode merge(ListNode a,ListNode b){
        if(a==null)return b;
        if(b==null)return a;
        ListNode newHead=new ListNode(-1);
        ListNode cur=newHead;
        ListNode list1=a;
        ListNode list2=b;
        while(list1!=null && list2!=null){
            if(list1.val<list2.val){
                cur.next=list1;
                list1=list1.next;
                cur=cur.next;
            }else{
                cur.next=list2;
                list2=list2.next;
                cur=cur.next;
            }
        }
        if(list1!=null){
            cur.next=list1;
        }else{
            cur.next=list2;
        }
        return newHead.next;
    }
}

在这里插入图片描述

合并两个链表

题目链接
思路
在这里插入图片描述

思路很简单,注释全在代码中了

class Solution {
    public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        ListNode node=list1;
        //让node跑到删除部分的头的左边一个
        for(int i=0;i<a-1;i++){
            node=node.next;
        }
        //nodeNext是删除部分的头
        ListNode nodeNext=node.next;
        //循环跑完.nodeNext在删除部分的结尾
        for(int i=0;i<b-a+1;i++){
            nodeNext=nodeNext.next;
        }
        //node的下一个接上list2
        node.next=list2;
        while(node.next!=null){
            node=node.next;
        }
        //list2结尾接上list1剩下的
        node.next=nodeNext;
        return list1;
    }
}

分割链表(特定值x)

题目链接
思路

创建新链表,让符合条件的节点接在后面,代码中注释很详细

class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode bs=null;//小数据部分始端(beforestart)
        ListNode be=null;//小数据部分末端(beforeend)
        ListNode as=null;//大数据部分始端(afterstart)
        ListNode ae=null;//大数据部分末端(afterend)
        ListNode cur=head;
        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=cur.next;
        }
        /**
         * 有两个小情况
         * ①:最后要返回小数据部分的头,但是如果小数据部分没有数据,就会出错
         * ②:找不到尾结点
         */
        if(bs==null){
            return as;
        }
        be.next=as;
        if(as!=null){
            ae.next=null;
        }
        return bs;

    }
}

在这里插入图片描述

排序链表

题目链接
思路

我们先把链表存在数组里
排序后再转成链表就可以了

class Solution {
    public ListNode sortList(ListNode head) {
        if(head==null||head.next==null){
            return head;
        }
        int len=getLength(head);
        int[] ans=toArray(head,len);
        Arrays.sort(ans);
        //创建个傀儡节点当头
        ListNode newHead=new ListNode(-1);
        ListNode cur=newHead;
        //再把数组转成链表
        for(int i=0;i<=len-1;i++){
            newHead.val=ans[i];
            if(i<len-1){
            //因为我们只创建了一个傀儡节点,所以接着创建
                newHead.next=new ListNode(-1);
                newHead=newHead.next;
            }
        }
        return cur;
    }
    //求链表长度
    public int getLength(ListNode head){
        int len=0;
        while(head!=null){
            len++;
            head=head.next;
        }
        return len;
    }
    //把链表存在数组里
    public int[] toArray(ListNode head,int len){
        int[] nums=new int[len];
        int i=0;
        while(head!=null){
            nums[i]=head.val;
            head=head.next;
            i++;
        }
        return nums;
    }
}

结果 😃
在这里插入图片描述

对链表进行插入排序

题目链接
思路

代码中注释超详细

class Solution {
    public ListNode insertionSortList(ListNode head){
        //判空
        if(head==null){
            return null;
        }
        ListNode node=new ListNode(-1);//创建傀儡节点,目的:方便插入
        node.next=head;//与头结点连接起来
        ListNode lastSorted=head;//
        ListNode cur=head.next;
        while(cur!=null){
            if(lastSorted.val<=cur.val){//排好序
                lastSorted=lastSorted.next;
            }else{
                ListNode prev=node;//要插入节点的前驱
                while(prev.next.val<=cur.val){
                    /**
                     * prev是要插入节点的前一个
                     * 如果prev下一个节点的val值大于cur的val值
                     * 就要把cur插入prev后面
                     * 这也是循环的退出条件
                     */
                    prev=prev.next;
                }
                //完成对cur的插入
                lastSorted.next=cur.next;//相当于是把要插入的节点在原位置删除,前后再连接起来
                cur.next=prev.next;
                prev.next=cur;
            }
            cur=lastSorted.next;//新的cur一直在lastSorted的后面
        }
        return node.next;
    }
}

在这里插入图片描述

链表的回文结构

题目链接
思路

快慢指针
1.找中间结点
2.中点到结尾进行反转
3.head从前往后走,slow从后往前走

class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head==null)return false;
        //定义快慢指针
        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;
        }
        //head从前往后
        //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;
    }
}

在这里插入图片描述

找出两个链表的第一个公共节点

题目链接
思路

代码中注释很详细

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null || headB==null) return null;
        int lenA=0;
        int lenB=0;
        ListNode l1=headA;
        ListNode l2=headB;
        //求长度
        while(l1!=null){
            l1=l1.next;
            lenA++;
        }
        while(l2!=null){
            l2=l2.next;
            lenB++;
        }
        //一定要指回来,不然下面会出现空指针异常
        l1=headA;
        l2=headB;
        int len=lenA-lenB;
        if(len<0){
             //l1永远放最长的链表
            len=lenB-lenA;
            l1=headB;
            l2=headA;
        }
         //让长链表先走len步
        while(len!=0){
            l1=l1.next;
            len--;
        }
         //往后遍历(相同了就停止)
        while(l1!=l2){
            l1=l1.next;
            l2=l2.next;
        }
        return l1;
    }
}

在这里插入图片描述

判断链表中是否有环

题目链接
思路

快慢指针

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){
            fast=fast.next.next;
            slow=slow.next;
            //如果两指针相遇,就说明一定有环
            if(fast==slow){
                return true;
            }
        }
        return false;
    }
}

在这里插入图片描述

返回链表开始入环的第一个节点

题目链接
思路

遍历链表每一个节点,并记录下来,遇到之前遍历过的,就可以得到入环节点

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode cur=head;
        Set<ListNode> set=new HashSet<>();
        while(cur!=null){
            if(set.contains(cur)){
                return cur;
            }else{
                set.add(cur);
            }
            //别忘了让cur动着
            cur=cur.next;
        }
        return null;
    }
}

在这里插入图片描述

两两交换链表中的节点

题目链接

思路

定义一个“前驱”,反转它后面的两个节点

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode newHead=new ListNode(-1);
        newHead.next=head;
        ListNode tmp=newHead;
        while(tmp.next!=null && tmp.next.next!=null){
            ListNode node1=tmp.next;
            ListNode node2=tmp.next.next;
            //反转
            tmp.next=node2;//tmp接上了第二个节点
            node1.next=node2.next;//第一个节点指向了第二个节点的后面(此时node1已经在后面了)
            node2.next=node1;//把node2接在新的node1上
            tmp=node1;//tmp再指向新的node1,进行下一个循环,交换下两个节点
        }
        return newHead.next;
    }
}

在这里插入图片描述

旋转链表

题目链接

思路

首尾相连闭合成环解决问题

在这里插入图片描述

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
    	//判空
        if(k==0 || head==null || head.next==null){
            return head;
        }
        int len=1;
        ListNode cur=head;
        //这里求链表长度
        //判断cur.next不为空,也可以保证下面※行不为空
        while(cur.next!=null){
            cur=cur.next;
            len++;
        }
        //pos是新链表
        int pos=len-k%len;
      ※cur.next=head;//首尾相连成环
      //这个循环结束,cur的位置在翻转后的链表的尾结点
        while(pos>0){
            cur=cur.next;
            pos--; 
        }
        //把环断开,尾结点置为空
        ListNode newHead=cur.next;
        cur.next=null;
        return newHead;
    }
}

在这里插入图片描述

两数相加

题目链接

思路

1.先将l1和l2头节点的值加起来赋值给新链表的头节点
2.遍历两个链表,只要有一个链表还没有遍历到末尾,就继续遍历
3.每次遍历生成一个当前节点cur的下一个节点,其值为两链表对应节点的和再加上当前节点cur产生的进位
4.更新进位后的当前节点cur的值
5.循环结束后,因为首位可能产生进位,因此如果cur.val是两位数的话,新增一个节点
6.返回头节点

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head=new ListNode(l1.val+l2.val);
        ListNode cur=head;
        while(l1.next!=null || l2.next!=null){
            l1=l1.next!=null ? l1.next : new ListNode();
            l2=l2.next!=null ? l2.next : new ListNode();
            cur.next=new ListNode(l1.val+l2.val+cur.val/10);
            cur.val%=10;
            cur=cur.next;
        }
        if(cur.val>=10){
            cur.next=new ListNode(cur.val/10);
            cur.val%=10;
        }
        return head;
    }
}

在这里插入图片描述

能看到这儿的人,都是月薪20k+的大佬
开怀大笑了,家人们
😄 😄 😄

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值