【代码随想录】链表

基础知识

链表的分类

在这里插入图片描述

链表的存储特点

数组:在内存中连续分布
链表:在内存中散乱地分布在各处,通过指针串联
在这里插入图片描述

链表的定义

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

203. 移除链表元素

方法一:迭代

不使用虚拟头节点

如果不使用虚拟头节点,则在删除节点时要对“要删除的节点是否为头节点”进行分类讨论:

  • 删除某个节点的做法是:令该节点的前一节点的next指针指向该节点的后一节点。如果该节点是链表头节点(没有前一节点),则直接令head指针指向该节点的后一节点。
  • 由于删除中需要知道要删除节点的前一节点,因此在遍历时使用两个指针一起遍历,快指针指向当前节点,慢指针指向当前节点的前一节点。

注意:在更新precur指针时,如果在本轮迭代中删除了cur节点,就只需要将cur向后移动一位,不需要移动pre

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode pre = null, cur = head;
        while(cur != null){
            if(cur.val == val){
                // 如果cur是要删除的节点
                if(cur == head){
                    // 如果cur是头节点
                    head = cur.next;
                }else{
                    // 如果cur不是头节点
                    pre.next = cur.next;
                }
                // 只需将cur指针向后移动一位
                cur = cur.next;
            }else{
                // 如果cur不是要删除的节点
                // 将两个指针一起向后移动一位
                pre = cur;
                cur = cur.next;
            }
        }
        return head;
    }
}

使用虚拟头节点

使用虚拟头节点可以不用对“要删除的节点是否为链表头节点”进行分类讨论,简化代码编写:

  • 开始遍历链表前创建一个虚拟头节点dummyHead,其next指针指向链表头节点
  • 最终返回dummyHead.next即可

注意:在更新precur指针时,如果在本轮迭代中删除了cur节点,就只需要将cur向后移动一位,不需要移动pre

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        // 添加一个虚拟头节点
        ListNode dummyHead = new ListNode(0, head);

        ListNode pre = dummyHead, cur = head;
        while(cur != null){
            if(cur.val == val){
                // 如果cur是要删除的节点
                pre.next = cur.next;
                // 只需将cur指针向后移动一位
                cur = cur.next;
            }else{
                // 如果cur不是要删除的节点
                // 将两个指针一起向后移动一位
                pre = cur;
                cur = cur.next;
            }
        }
        return dummyHead.next;
    }
}

方法二:递归

在这里插入图片描述

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        // 递归终点:空链表
        if(head == null){
            return head;
        }

        // 对除了头节点head以外的节点进行删除操作
        ListNode node = removeElements(head.next, val);
        // 判断头节点head是否需要删除(判断head的节点值是否等于给定的val)
        if(head.val == val){
            // 如果head.val等于val,则需要删除头节点
            head = node;
        }else{
            // 如果head.val不等于val,则不需要删除头节点
            head.next = node;
        }
        return head;
    }
}

707. 设计链表

class MyLinkedList {

    ListNode head;

    public MyLinkedList() {
        head = null;
    }
    
    // 获取指定下标位置节点的值
    public int get(int index) {
        ListNode node = getNodeByIndex(index);
        if(node == null){
            return -1;
        }else{
            return node.val;
        }
    }

    // 获取指定下标的节点
    private ListNode getNodeByIndex(int index){
        // 如果当前链表为空
        if(head == null){
            return null;
        }

        // 如果指定下标不合法
        if(index < 0){
            return null;
        }

        // 如果链表不为空、且指定下标合法
        // 从head开始遍历链表,index值代表还需遍历的元素个数
        ListNode curNode = head;
        while(true){
            // 如果index已经减少到0了,说明找到指定下标的节点了
            if(index == 0){
                return curNode;
            }

            // 如果index还没有减少到0,但已经抵达链表结尾了,说明链表中不存在该下标的节点
            if(curNode == null){
                return null;
            }

            // 将遍历指针后移一位,并将index值减少1
            curNode = curNode.next;
            index--;
        }
    }
    
    public void addAtHead(int val) {
        ListNode newNode = new ListNode(val, head);
        head = newNode;
    }
    
    public void addAtTail(int val) {
        // 如果当前数组为空
        if(head == null){
            addAtHead(val);
            return;
        }

        // 找到当前数组中的最后一个元素
        ListNode lastNode = head;
        while(lastNode.next != null){
            lastNode = lastNode.next;
        }
        ListNode newNode = new ListNode(val);
        lastNode.next = newNode;
    }
    
    public void addAtIndex(int index, int val) {
        if(index < 0){
            return;
        }

        if(index == 0){
            addAtHead(val);
        }

        // 找到要插入位置的前一个位置的节点
        ListNode preNode = getNodeByIndex(index - 1);

        if(preNode == null){
            return;
        }

        ListNode newNode = new ListNode(val, preNode.next);
        preNode.next = newNode;
    }
    
    public void deleteAtIndex(int index) {
        if(head == null){
            return;
        }

        if(index < 0){
            return;
        }

        if(index == 0){
            head = head.next;
            return;
        }

        // 找到要插入位置的前一个位置的节点
        ListNode preNode = getNodeByIndex(index - 1);

        if(preNode == null){
            return;
        }

        if(preNode.next == null){
            return;
        }

        preNode.next = preNode.next.next;
    }

    // 链表节点
    class ListNode{
        int val;
        ListNode next;
        ListNode(){}
        ListNode(int val){this.val = val;}
        ListNode(int val, ListNode next){this.val = val; this.next = next;}
    }
}

19. 删除链表的倒数第N个节点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 为了删除倒数第N个节点,需要找到链表的倒数第N+1个节点(即倒数第N个节点的前一节点)
        /* 寻找链表倒数第N+1个节点的思路:
         * 用快慢指针同时遍历链表,fast指针永远比slow指针快n步(即当slow指向第i个节点时,fast指向第i+n个节点)
         * 当fast指针指向链表最后一个节点时,slow指针刚好指向倒数第N个节点的前一节点
         */

        // 添加一个虚拟头节点
        ListNode dummyHead = new ListNode(0, head);

        
        ListNode slow = dummyHead; // 初始化slow指针
        // 初始化fast指针,令其指向第n个节点
        ListNode fast = dummyHead; 
        for(int i=1; i<=n; i++){
            fast = fast.next;
            if(fast == null){
                // 链表中节点个数少于n
                return head;
            }
        }

        // 向后移动指针,直到fast指针指向链表最后一个节点
        while(fast.next != null){
            slow = slow.next;
            fast = fast.next;
        }

        // 删除slow指向节点的下一节点
        slow.next = slow.next.next;

        return dummyHead.next;
    }
}

面试题 02.07. 链表相交

解法一:哈希表

在这里插入图片描述

解法二:双指针

在这里插入图片描述在这里插入图片描述

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 如果A和B中有空链表,则两个链表必然没有交点
        if(headA == null || headB == null){
            return null;
        }

        // 指针A先遍历链表A,再遍历链表B
        // 指针B先遍历链表B,再遍历链表A
        ListNode A = headA, B = headB;
        while(A != B){
            A = (A == null ? headB : A.next);
            B = (B == null ? headA : B.next);
        }
        // 两指针相遇
        return A;
    }
}

环形链表

141. 环形链表 Ⅰ

解法一:哈希表

最容易想到的方法是遍历所有节点,每次遍历到一个节点时,判断该节点此前是否被访问过。

具体地,我们可以使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。

解法二:双指针 - 快慢指针

  • 快慢指针:fast每次走两步,slow每次走一步
  • 如果无环:fast指针能够抵达链表末尾的null
  • 如果有环:fast和slow会在环中相遇
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }

        // fast每次走两步,slow每次走一步
        ListNode slow = head, fast = head.next;
        while(true){
            if(slow == fast){
                // 快慢指针相遇 => 链表中有环
                return true;
            }

            // fast向后走两步
            if(fast == null){
                // fast指针抵达链表末尾的null => 链表中无环
                return false;
            }else{
                fast = fast.next;
            }
            if(fast == null){
                // fast指针抵达链表末尾的null => 链表中无环
                return false;
            }else{
                fast = fast.next;
            }

            // slow指针向后走一步
            slow = slow.next;
        }
    }
}

142. 环形链表 II

如果链表中有环,当二者第一次相遇时:
在这里插入图片描述在这里插入图片描述具体做法:
在这里插入图片描述需要注意的是,这里的第一步其实应该是让fast指向头节点的前一个位置,这样当fast再走a步就可以到达环的入口。
也可以先让fast指向头节点,让slow指向slow.next,然后两个指针再一起走a-1步,但是这时会忽略“第一次相遇时slow指向的正好就是环的入口”这种情况,需要分类讨论。

在这里插入图片描述
在这里插入图片描述

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null){
            return null;
        }

        // fast每次走两步,slow每次走一步
        ListNode slow = head, fast = head.next;
        while(true){
            if(slow == fast){
                // 快慢指针相遇 => 链表中有环
                if(slow != head){
                    fast = head;
                    slow = slow.next;
                    while(fast != slow){
                        fast = fast.next;
                        slow = slow.next;
                    }
                }
                return slow;
            }

            // fast向后走两步
            if(fast == null){
                // fast指针抵达链表末尾的null => 链表中无环
                return null;
            }else{
                fast = fast.next;
            }
            if(fast == null){
                // fast指针抵达链表末尾的null => 链表中无环
                return null;
            }else{
                fast = fast.next;
            }

            // slow指针向后走一步
            slow = slow.next;
        }
    }
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值