关于面试中链表必考经典笔试题--小总结

9 篇文章 0 订阅

以下总结的是面试中必然会考到的经典的链表题,欢迎阅读~

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

LeetCode 203.移除链表元素
在这里插入图片描述

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null){
            return null;
        }
        if (head.val == val){
            head = head.next;
        }
        ListNode prev = head;
        ListNode cur = head.next;
        while(cur != null){
            if(cur.val == val){
                prev.next = cur.next;
                cur = prev.next;
            }else{
                prev = prev.next;
                cur = cur.next;
            }
        }
        return head;
    }
}

注意: 上述代码有一处错误,代码执行结果没有问题,但是一提交就显示如下
在这里插入图片描述
可以明显看到输出结果为[7],按照题目要求正确输出结果应该是[],那么为什么会出现这样的结果呢?

  • 原因在于第二个ifListNode cur = head.next;
    如果头结点就是值为val的节点,那么根据head = head.next;操作删除头结点
    但是后面的ListNode cur = head.next;会直接跳过第二个节点去到第三个节点,假设链表中所有节点的值和要求删除的值全部都是一样的,那么在删除第一个节点之后,第二个节点就是head,由cur = head.nextcur直接指向第三个节点,那么程序运行删除第三个节点,以此类推…就可以得出,漏掉了第二个节点,就如上图所示解答错误,输出结果为[7]

那么我们针对此问题修改一下代码
正确代码如下(三种方法)

//直接把判断头结点这种特殊情况挪到一般情况后面去(这种方法特别简便)
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null){
            return null;
        }
        ListNode prev = head;//prev 待删除节点的前一个节点
        ListNode cur = head.next;//cur 待删除节点
        while(cur != null){
            if(cur.val == val){//如果找到了值为val的节点
                prev.next = cur.next;//删除这个节点
                cur = prev.next;//更新cur的位置
            }else{//没有找到值为val的节点
                prev = prev.next;//更新prev和cur的位置
                cur = cur.next;
            }
        }
        //如果头结点的值为val,即直接删除头结点
        if(head.val == val){
            head = head.next;
        }
        return head;
    }
}
//或者直接判断条件改成循环挨个遍历,这样就不会漏掉第二个节点
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null){
            return null;
        }
        while(head.val == val){
            head = head.next;
            if(head == null){
                return null;
            }
        }
        ListNode prev = head;
        ListNode cur = head.next;
        while(cur != null){
            if(cur.val == val){
                prev.next = cur.next;
                cur = prev.next;
            }else{
                prev = prev.next;
                cur = cur.next;
            }
        }
        return head;
    }
}
//也可以使用傀儡节点统一操作
//迭代
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode dummyNode = new ListNode(0);
        dummyNode.next = head;
        ListNode cur = dummyNode;
         while(cur.next != null){
            if(cur.next.val == val){
                cur.next = cur.next.next;//相当于删除cur.next这个节点
            }else{
                cur = cur.next;
            }
        } 
        return dummyNode.next;
    }
}

2. 反转一个单链表

LeetCode 206.反转链表
在这里插入图片描述
在这里插入图片描述

class Solution31 {
    public ListNode reverseList(ListNode head) {
        if(head == null){
            return null;
        }
        //如果只有一个节点,就直接返回该节点
        if(head.next == null){
            return head;
        }
        ListNode newHead = null;
        ListNode prevNode = null;
        ListNode curNode = head;
        ListNode nextNode = curNode.next ;
        while(curNode != null){
            //每次都需要更新当前节点的下一个节点的位置
            nextNode = curNode.next;
            //只有两个节点,那么curNode就指向最后一个节点
            //新的头节点newHead就是curNode
            if(nextNode == null){
                newHead = curNode;
            }
            curNode.next = prevNode;
            //更新prevNode和curNode的位置
            prevNode = curNode;
            curNode = nextNode;
        }
        return newHead;
    }
}


3. 链表的中间结点

LeetCode 876.链表的中间结点
在这里插入图片描述

解题思路:
1.得出链表的长度length / 2
2.从头结点cur遍历,cur走length / 2步
3.返回该结点

例如:
[1, 2, 3, 4, 5]长度为2
length / 2 = 5 / 2 = 2
所以cur走2步到达 [3]
故返回[3]

class Solution32 {
    public int getLength(ListNode head){
        int length = 0;
        for(ListNode cur = head; cur != null; cur = cur.next){
            length++;
        }
        return length;
    }
    public ListNode middleNode(ListNode head) {
        if(head == null){
            return null;
        }
        int length = getLength(head);
        int steps = length / 2;
        ListNode cur = head;
        for(int i = 0; i < steps; i++){//若是i = 1,那么i <= steps
            cur = cur.next;
        }
        return cur;
    }
}


4. 链表中倒数第k个节点

NowCoder 链表中倒数第k个结点
在这里插入图片描述

解题思路:
1.链表长度length
2.倒数第k个节点 = 第 length - k 个节点
3.从头节点走 length - k 步
4.返回该节点
注意:单链表只能从前往后

public class Solution {
    public int getLength(ListNode head){
        int length = 0;
        for(ListNode cur = head; cur != null; cur = cur.next){
            length++;
        }
        return length;
    }
    public ListNode FindKthToTail(ListNode head,int k) {
        int length = getLength(head);
        if(head == null || k <= 0 || k > length){
            return null;
        }
        int steps = length - k;
        ListNode cur = head;
        for(int i = 0; i < steps; i++){//若是i = 1,那么i <= steps
            cur = cur.next;
        }
        return cur;
    }
}

5. 合并两个有序链表

LeetCode 21.合并两个有序链表
在这里插入图片描述
在这里插入图片描述

解题思路:
1.遍历两个链表
2.在新建的链表中进行尾插操作
3.返回上述新建的链表
(创建一个傀儡节点统一操作)


class Solution {
       public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null){
            return null;
        }
        if(list2 == null){
            return null;
        }
        ListNode cur1 = list1;
        ListNode cur2 = list2;
        ListNode newHead = null;
        ListNode newTail = newHead;
        while(cur1 !=null && cur2 != null){
            if(cur1.val < cur2.val){
                if(newTail == null){
                    newHead = cur1;
                    newTail = newHead;
                }else{
                    newTail.next = cur1;
                    newTail = newTail.next;
                }
            }else{
                if(newTail == null){
                    newHead = cur2;
                    newTail = newHead;
                }else{
                    newTail.next = cur2;
                    newTail = newTail.next;
                }
            }
        }
        if(cur1 == null){
            newTail.next = cur2;
        }else{
            newTail.next = cur1;
        }
        return newHead.next;
    }
}

上述代码比较容易理解,但是提交显示超出时间限制
优化代码如下

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        ListNode cur1 = list1;
        ListNode cur2 = list2;
        //创造一个傀儡节点
        ListNode newHead = new ListNode(0);
        ListNode newTail = newHead;
        while(cur1 !=null && cur2 != null){
            if(cur1.val < cur2.val){
                    newTail.next = cur1;
                    newTail = newTail.next;
                    cur1 = cur1.next;
            }else{
                    newTail.next = cur2;
                    newTail = newTail.next;
                    cur2 = cur2.next;
            }
        //也可以将判断条件里的newTail = newTail.next;提出来放在这里
        }
        if(cur1 == null){
            newTail.next = cur2;
        }else{
            newTail.next = cur1;
        }
        return newHead.next;
    }
}

6. 链表分割

NowCoder CM11l链表分割
在这里插入图片描述

解题思路:
1.创建两个新的链表small和large
2.small存放比x小的数,large存放比x大的数
3.将两个链表连接在一起

public class Partition {
    public ListNode partition(ListNode pHead, int x) {
        if(pHead == null){
            return null;
        }
        if(pHead.next == null){
            return pHead;
        }
        //创建的新的两个链表带有傀儡节点
        ListNode smallHead = new ListNode(0);
        ListNode smallTail = smallHead;
        ListNode largeHead = new ListNode(0);
        ListNode largeTail = largeHead;
        for(ListNode cur = pHead; cur != null;cur = cur.next){
            if(cur.val < x){
                smallTail.next = new ListNode(cur.val);
                smallTail = smallTail.next;
            }else{
                largeTail.next = new ListNode(cur.val);
                largeTail = largeTail.next;
            }
        }
        smallTail.next = largeHead.next;
        return smallHead.next;
    }
}

为什么连接两个链表的代码是smallTail.next = largeHead.next; 而不是smallTail.next = largeHead;?

  • 如果是smallTail.next = largeHead;则返回的链表中的节点存在傀儡节点,这个傀儡节点就是large链表中的傀儡节点largeHead

注:
亮点在于smallTail.next = new ListNode(cur.val);argeTail.next = new ListNode(cur.val);
创建一个 存放的值 为 cur存放的值 的新的的节点进行尾插操作
这样避免for循环接下来的遍历出现有可能出现的问题


7. 删除链表中重复的结点

NowCoder JZ76删除链表中重复的结点
在这里插入图片描述
在这里插入图片描述

public class Solution35 {
    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead == null){
            return null;
        }
        if(pHead.next == null){
            return pHead;
        }
        ListNode newHead = new ListNode(0);
        ListNode newTail = newHead;
        ListNode cur = pHead;
        while(cur != null){//不能把cur.next != null放在while中,会使题中例子最后一个节点没有被输出
            if(cur.next != null && cur.val == cur.next.val){
                while(cur != null && cur.next != null && cur.val == cur.next.val){
                    cur = cur.next;
                }
                cur = cur.next;
            }else{
                newTail.next = new ListNode(cur.val);
                newTail = newTail.next;
                cur = cur.next;
            }
        }
        return newHead.next;
    }
}

只需要注意注释中那个小问题即可


8. 链表的回文结构

NowCoder OR36链表的回文结构
在这里插入图片描述

解题思路1:
1.将链表A复制成新链表B
2.将新链表B逆置
3.比较两个链表是否一样

//这种办法可以通过,但是空间复杂度为O(N)
public class PalindromeList {
    public boolean chkPalindrome(ListNode A) {
        if(A == null || A.next == null){
            return true;
        }
        ListNode newHead = new ListNode(0);
        ListNode newTail = newHead;
        for(ListNode cur = A; cur != null; cur = cur.next){
            newTail.next = new ListNode(cur.val);
            newTail = newTail.next;
        }
        ListNode B = newHead.next;  //1.
        ListNode prev = null;
        ListNode cur = B;
        while(cur != null){
            ListNode next = cur.next;
            if(next == null){
                B = cur;
            }
            cur.next = prev;
            prev = cur;
            cur = next;
        }   //2.
        ListNode cur1 = A;
        ListNode cur2 = B;
        while(cur1 != null && cur2 != null){
            if(cur1.val != cur2.val){
                return false;
            }
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return true;  //3.
    }
}

解题思路2:
1.找到中间节点
2.将后半部分逆置
3.比较前后两半部分是否一样

//空间复杂度为O(1)
public class PalindromeList2 {
    public int getLength(ListNode head){
        int length = 0;
        for(ListNode cur = head; cur != null; cur = cur.next){
            length++;
        }
        return length;
    }
    public boolean chkPalindrome(ListNode A) {
        if(A == null || A.next == null){
            return true;
        }
        int length = getLength(A);
        int steps = length / 2;
        ListNode B = A;
        for(int i = 0; i < steps; i++){
            B = B.next;
        }   //1.
        ListNode prev = null;
        ListNode cur = B;
        while(cur != null){
            ListNode next = cur.next;
            if(next == null){
                B = cur;
            }
            cur.next = prev;
            prev = cur;
            cur = next;   //2.
        }
        ListNode cur1 = A;
        ListNode cur2 = B;
        while(cur != null && cur2 != null){
            if(cur1.val != cur2.val){
                return false;
            }
            cur1 = cur1.next;
            cur2 = cur2.next;
        }   //3.
        return true;
    }
}

9. 相交链表

LeetCode 160.相交链表
在这里插入图片描述
在这里插入图片描述

解题思路1:
1.分别求出两个链表的长度len1,len2
2.分别创建两个引用cur1,cur2,指向两个链表的头结点
3.看哪个链表长,如果len1 > len2,就让cur1先走len1 - len2步; 如果是len1 < len2,就让cur2先走len2 - len1
4.此时让cur1cur2同时往后走,看是否相遇,当cur1cur2相遇的时候,这个位置就是链表的交点

public class Solution36 {
    public int getLength(ListNode head){
        int length = 0;
        for(ListNode cur = head; cur != null; cur = cur.next){
            length++;
        }
        return length;
    }
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int len1 = getLength(headA);
        int len2 = getLength(headB);   //1.
        if(len1 > len2){
            int steps = len1 - len2;
            for(int i = 0; i < steps; i++){
                headA = headA.next;
            }
        }else{
            int steps = len2 - len1;
            for(int i = 0; i < steps; i++){
                headB = headB.next;
            }
        }   //3.
        while(headA != null && headB != null){
            if(headA == headB){
                return headA;
            }
            headA = headA.next;
            headB = headB.next;
        }   //4.
        return null;
    }
}

解题思路2:
1.先把链表A添加进set
2.再对链表B的节点挨个进行查找,如果B的节点和set中的节点有相同的,则此节点就是链表的交点

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> set = new HashSet<ListNode>();
        ListNode tmp = headA;
        while(tmp != null){
            set.add(tmp);
            tmp = tmp.next;
        } 
        tmp = headB;
        while(tmp != null){
            if(set.contains(tmp)){
                return tmp;
            }
            tmp = tmp.next;
        }
        return null;
    }
}

解题思路3:

我走过你走过的路,你走过我走过的路,我们就相遇了。

A链表节点数量: a
B链表节点数量: b
A链表和B链表共同节点数量: c
A链表头结点到交点处节点数量: a - c
B链表头结点到交点处节点数量: b - c
构造两个节点指针A , B
指针A遍历完A链表再遍历B链表, 走到到交点处共走步数: a + ( b - c)
指针B遍历完B链表再遍历A链表, 走到到交点处共走步数: b + ( a - c)
此时指针A , B重合, 有两种情况
①两个链表有公共尾部: 指针A , B同时指向交点
②两个链表无公共尾部: 指针A , B同时指向null

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode A = headA, B = headB;
        while(A != B){
            A = A != null ? A.next : headB;
            B = B != null ? B.next : headA;
        }
        return B;
    }
}

关于三元运算符(即上述方法中的问号与冒号)


10. 环形链表

LeetCode 141.环形链表
在这里插入图片描述
在这里插入图片描述

解题思路:
快慢指针
快指针走两步,慢指针走一步,最终一定会相遇,代表有环

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;
    }
}

11. 环形链表②

LeetCode 142.环形链表
在这里插入图片描述
在这里插入图片描述

解题思路:
快慢指针
快指针走两步,慢指针走一步,最终一定会相遇,代表有环
1.先找到快慢指针重合的位置
2.如果带环,从链表头和fast slow交汇的位置同时出发,两个引用相遇的位置就是环的入口(即链表头到环入口的节点数量 = fast slow交汇的位置到环入口的节点数量)

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                break;
            }
        }    //1.
        if(fast == null || fast.next == null){
            //说明链表不带环
            return null;
        }
        ListNode cur1 = head;
        ListNode cur2 = fast;
        while(cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;    //2.
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值