[代码随想录] 链表总结

[代码随想录] 链表总结

链表基础知识

链表定义

链表是一种通过指针串在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
在这里插入图片描述

链表类型

这里进行图片展示,通过图片理解

单链表

在这里插入图片描述

双链表

在这里插入图片描述

循环链表

在这里插入图片描述

链表的存储方式

数组在内存中是连续分布的,链表在内存中存储不是连续分布的,通过指针域的指针链接各个节点
在这里插入图片描述

java中链表节点的表示

public class ListNode {
    // 结点的值
    int val;

    // 下一个结点
    ListNode next;

    // 节点的构造函数(无参)
    public ListNode() {
    }

    // 节点的构造函数(有一个参数)
    public ListNode(int val) {
        this.val = val;
    }

    // 节点的构造函数(有两个参数)
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

链表算法

在这之前先清楚一件事,leetcode中的题目传入的链表都没有头节点 ,传入的都是链表中的第一个节点,这个需要清楚

移除链表元素

203.移除链表元素

力扣题目链接

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
在这里插入图片描述
这道题比较简单,属于基础的对链表的操作,直接展示代码。需要注意的是,使用虚拟头节点可直接从虚拟头节点遍历链表,最后返回虚拟头节点的next;不使用虚拟头节点则需要保证head的值不等于val,返回head即可

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
// 创建虚拟头节点
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null) {
            return head;
        }
        ListNode res = new ListNode(-1, head);
        ListNode pre = res, cur = head;
        while(cur != null) {
            if(cur.val == val) {
                pre.next = cur.next;
            }else {
                pre = cur;
            }
            cur = cur.next;
        }
        return res.next;
    }
}
 // 不使用虚拟头节点
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        while(head != null && head.val == val) {
            head = head.next;
        }
        if(head == null) {
            return head;
        }
        ListNode pre = head;
        ListNode cur = head.next;
        while(cur != null) {
            if(cur.val == val) {
                pre.next = cur.next;
            }else {
                pre = cur;
            }
            cur = cur.next;
        }
        return head;
    }
}

设计链表

707.设计链表

力扣题目链接

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index有效,则删除链表中的第 index 个节点。
示例:
MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2);   //链表变为1-> 2-> 3
linkedList.get(1);            //返回2
linkedList.deleteAtIndex(1);  //现在链表是1-> 3
linkedList.get(1);            //返回3

该道题目主要是对链表的操作实现,包括插入,删除,查询。这里可以将addAtHead(val)、addAtTail(val)两个函数看作特殊情况的addAtIndex(index,val)。addAtHead(val)是在链表头部插入,即addAtIndex(0, val);addAtTail(val)是在链表的尾部插入,即addAtIndex(size, val)。定义size来维护链表的长度。所有的操作都是链表基础操作,耐心便不难写出。

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

class MyLinkedList {
    
    int size;
    ListNode head;

    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }
    
    public int get(int index) {
        if(index < 0 || index >= size) {
            return -1;
        }
        ListNode cur = head;
        for(int i = 0;i <= index;i++) {
            cur = cur.next;
        }
        return cur.val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }
    
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }
    
    public void addAtIndex(int index, int val) {
        if(index > size) return;
        if(index < 0) index = 0;
        ListNode cur = head;
        // 链表下标表示同数组下标
        // i < index找到的是index前一个节点,i <= index找到的是index个节点
        for(int i = 0;i < index;i++) {
            cur = cur.next;
        }
        ListNode newNode = new ListNode(val);
        newNode.next = cur.next;
        cur.next = newNode;
        size++;
    }
    
    public void deleteAtIndex(int index) {
        if(index < 0 || index >= size) return;
        size--;
        if(index == 0) {
            head = head.next;
            return;
        }
        ListNode cur = head;
        for(int i = 0;i < index;i++) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

翻转链表

206.反转链表

力扣题目链接

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

在这里插入图片描述

双指针算法

这道题可以定义两个指针,一前一后指向两个节点,然后在定义一个temp存储后指针的下一个节点,两个指针指向的节点翻转,然后两个指针同时后移。

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        // cur当前节点,pre前一个节点
        ListNode temp = null, pre = null;
        ListNode cur = head;
        while(cur != null) {
            temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}
递归算法

这道题也可以递归实现,通过递归步进指针

// 递归 
class Solution {
    public ListNode reverseList(ListNode head) {
        return reverse(null, head);
    }

    private ListNode reverse(ListNode prev, ListNode cur) {
        if (cur == null) {
            return prev;
        }
        ListNode temp = null;
        temp = cur.next;// 先保存下一个节点
        cur.next = prev;// 反转
        return reverse(cur, temp);
    }
}

两两交换链表中的节点

24. 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
在这里插入图片描述

双指针实现

这道题可以通过定义两个指针,一个temp指针临时存放下一步的节点,通过三个指针之间的操作实现链表两两翻转,和上边的翻转链表题目有点类似。三个指针的操作如下图
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
// 虚拟头结点
class Solution {
  public ListNode swapPairs(ListNode head) {

    ListNode dummyNode = new ListNode(0);
    dummyNode.next = head;
    ListNode prev = dummyNode;

    while (prev.next != null && prev.next.next != null) {
      ListNode temp = head.next.next; // 缓存 next
      prev.next = head.next;          // 将 prev 的 next 改为 head 的 next
      head.next.next = head;          // 将 head.next(prev.next) 的next,指向 head
      head.next = temp;               // 将head 的 next 接上缓存的temp
      prev = head;                    // 步进1位
      head = head.next;               // 步进1位
    }
    return dummyNode.next;
  }
}
递归实现

这道题也可以用递归的思想实现,先让两个指针递归到链表尾部,然后在回溯的过程中再两两翻转节点

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode next = head.next;
        // 递归
        ListNode newNode = swapPairs(next.next);
        // 交换
        next.next = head;
        head.next = newNode;
        
        return next;
    }
}

删除链表中倒数第n个节点

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

力扣题目链接

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

在这里插入图片描述
这道题最快想到的暴力解法应该是先遍历整个链表,求到链表的长度,然后根据n值求的倒数指针到达倒数第n+1个节点需要多少步,然后再删除节点。但是这样比较太冗余,所有采用双指针解法。
快慢指针解决思路,快指针和慢指针之间相差n - 1个结点,当快指针到达最后一个节点时(fast.next == null),慢指针指向的便为倒数第n个节点的前一个节点。(先让快指针先走n步,实现相差n-1个节点)

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        ListNode slow = dummy;
        ListNode fast = dummy;
        for(int i = 0;i < n;i++) {
            fast = fast.next;
        }
        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        
        return dummy.next;
    }
}

链表相交

07. 链表相交

力扣题目链接

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交:
在这里插入图片描述

  • 题目数据 保证 整个链式结构中不存在环。
  • 注意,函数返回结果后,链表必须 保持其原始结构 。

在这里插入图片描述
这道题需要先求两个链表的长度,通过长度差值让长链表先走差值步,让两个链表对齐,然后两个链表同时前进,如果相交则某一时刻两个指针会相等,不相交则返回空

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0,lenB = 0;
        while(curA != null) {
            lenA++;
            curA = curA.next;
        }
        while(curB != null) {
            lenB++;
            curB = curB.next;
        }
        curA = headA;
        curB = headB;
        // 让curA始终作为长链表
        if(lenB > lenA) {
            int lenTemp = lenA;
            lenA = lenB;
            lenB = lenTemp;
            ListNode nodeTemp = curA;
            curA = curB;
            curB = nodeTemp;
        }
        for(int i = 0;i < lenA - lenB;i++) {
            curA = curA.next;
        }
        while(curA != null) {
            if(curA == curB) {
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }
}

环形链表

142.环形链表II

力扣题目链接
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

  • 不允许修改链表

在这里插入图片描述
判断链表是否有环很简单,经典快慢指针,让快指针每次比慢指针多步进一步,如果存在环的话快慢两个指针一定会相遇。难点在于判断环的入口,当有环快慢指针相遇时,可通过从链表头部出发一个指针,相遇节点出发一个指针,两个指针同时一步一步前进,相遇时该节点就位环的入口

接下来是判断环的入口的数学验证:
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
在这里插入图片描述
那么相遇时: slow指针走过的节点数为:x + y,fast指针走过的节点数为:x + y + n(y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:

(x + y) * 2 = x + y + n (y + z)

两边消掉一个(x+y): x + y = n (y + z)

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。

所以要求x ,将x单独放在左面:x = n (y + z) - y ,

再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

这个公式说明什么呢?

先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当 n为1的时候,公式就化解为 x = z,

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。

那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if(fast == slow) {
                ListNode index1 = head;
                ListNode index2 = slow;
                while(index2 != index1) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}

总结

这个图是 代码随想录知识星球 成员:海螺人,所画,总结的非常好,分享给大家
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值