链表的基础知识

本文介绍了链表的基础知识,包括单向链表、双向链表和循环链表的分类,以及链表的程序实现,如构造、排序(自顶向下和自底向上归并排序)和双指针操作,特别讨论了使用双指针删除链表倒数第n个节点的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

一种线性表数据结构。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。

1、分类

单向链表

包含指针的链表节点、内存地址

双向链表

包含前驱节点和后继节点的链表节点、内存地址

循环链表

将单链表中的终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,使得链表的头尾相接,这种链表称之为单循环链表

二、程序实现

1、构造链表

class ListNode:
    def __init__(self, val):
        self.val = val
        self.next = None
class MyLinkedList:
    def __init__(self):
        self.size = 0
        self.head = ListNode(0)  # sentinel node as pseudo-head    
    def get(self, index):
        # if index is invalid
        if index < 0 or index >= self.size:
            return -1  
        curr = self.head
        # index steps needed 
        # to move from sentinel node to wanted index
        for _ in range(index + 1):
            curr = curr.next
        return curr.val          

    def addAtHead(self, val):
        self.addAtIndex(0, val)
        
    def addAtTail(self, val):
        """
        Append a node of value val to the last element of the linked list.
        """
        self.addAtIndex(self.size, val)
        
    def addAtIndex(self, index, val):
        # If index is greater than the length, 
        # the node will not be inserted.
        if index > self.size:
            return
        
        # [so weird] If index is negative, 
        # the node will be inserted at the head of the list.
        if index < 0:
            index = 0
        
        self.size += 1
        # find predecessor of the node to be added
        pred = self.head
        for _ in range(index):
            pred = pred.next
            
        # node to be added
        to_add = ListNode(val)
        # insertion itself
        to_add.next = pred.next
        pred.next = to_add
        

    def deleteAtIndex(self, index):
        """
        Delete the index-th node in the linked list, if the index is valid.
        """
        # if the index is invalid, do nothing
        if index < 0 or index >= self.size:
            return
        
        self.size -= 1
        # find predecessor of the node to be deleted
        pred = self.head
        for _ in range(index):
            pred = pred.next
            
        # delete pred.next 
        pred.next = pred.next.next

2、链表排序

1)自顶向下归并排序

对链表自顶向下归并排序的过程如下。

找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 22 步,慢指针每次移动 11 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。

对两个子链表分别排序。

将两个排序后的子链表合并,得到完整的排序后的链表。可以使用「21. 合并两个有序链表」的做法,将两个有序的子链表进行合并。

上述过程可以通过递归实现。递归的终止条件是链表的节点个数小于或等于 11,即当链表为空或者链表只包含 11 个节点时,不需要对链表进行拆分和排序。

2)自底向上归并排序

使用自底向上的方法实现归并排序,则可以达到 O(1)的空间复杂度。

首先求得链表的长度length,然后将链表拆分成子链表进行合并。

具体做法如下。

用 subLength 表示每次需要排序的子链表的长度,初始时 subLength=1。

每次将链表拆分成若干个长度为subLength 的子链表(最后一个子链表的长度可以小于 subLength),按照每两个子链表一组进行合并,合并后即可得到若干个长度为subLength×2 的有序子链表(最后一个子链表的长度可以小于subLength×2)。合并两个子链表仍然使用「21. 合并两个有序链表」的做法。

将subLength 的值加倍,重复第 2 步,对更长的有序子链表进行合并操作,直到有序子链表的长度大于或等于length,整个链表排序完毕。

如何保证每次合并之后得到的子链表都是有序的呢?可以通过数学归纳法证明。

初始时subLength=1,每个长度为 11 的子链表都是有序的。

如果每个长度为 subLength 的子链表已经有序,合并两个长度为 subLength 的有序子链表,得到长度为subLength×2 的子链表,一定也是有序的。

当最后一个子链表的长度小于 subLength 时,该子链表也是有序的,合并两个有序子链表之后得到的子链表一定也是有序的。

因此可以保证最后得到的链表是有序的。

3、双指针

双指针(Two Pointers):指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞时针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。

而在单链表中,因为遍历节点只能顺着 next 指针方向进行,所以对于链表而言,一般只会用到「快慢指针」和「分离双指针」。其中链表的「快慢指针」又分为「起点不一致的快慢指针」和「步长不一致的快慢指针」。

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

我们也可以在不预处理出链表的长度,以及使用常数空间的前提下解决本题。

由于我们需要找到倒数第 nn 个节点,因此我们可以使用两个指针first 和second 同时对链表进行遍历,并且 first 比second 超前 n 个节点。当first 遍历到链表的末尾时second 就恰好处于倒数第 n 个节点。

具体地,初始时first 和second 均指向头节点。我们首先使用first 对链表进行遍历,遍历的次数为 n。此时,first 和 second 之间间隔了 n−1 个节点,即 first 比second 超前了 n 个节点。

在这之后,我们同时使用first 和 second 对链表进行遍历。当first 遍历到链表的末尾(即 first 为空指针)时,second 恰好指向倒数第 n 个节点。

class Solution:
    def removeNthFromEnd(self, head, n):
        dummy = ListNode(0, head)
        first = head
        second = dummy
        for i in range(n):
            first = first.next

        while first:
            first = first.next
            second = second.next
        
        second.next = second.next.next
        return dummy.next

                                                                                                                             

参考文献

通俗易懂讲解 链表 - 知乎

02.链表 | 算法通关手册

循环链表的创建、遍历_Sun.ME的博客-优快云博客_循环链表的创建

设计链表 - 设计链表 - 力扣(LeetCode) (leetcode-cn.com)

排序链表 - 排序链表 - 力扣(LeetCode) (leetcode-cn.com)

https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/solution/shan-chu-lian-biao-de-dao-shu-di-nge-jie-dian-b-61/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值