【链表】(一) 单链表

目录

一、简介 - 单链表

二、添加操作 - 单链表

三、删除操作 - 单链表 

四、设计链表

4.1 题目要求

4.2 解决过程

4.2.1 单链表

4.2.2 双链表


一、简介 - 单链表

// Definition for singly-linked list.
struct SinglyListNode {
    int val;
    SinglyListNode *next;
    SinglyListNode(int x) : val(x), next(NULL) {}
};

 

参考文献:https://leetcode-cn.com/explore/learn/card/linked-list/193/singly-linked-list/738/


二、添加操作 - 单链表

参考文献:https://leetcode-cn.com/explore/learn/card/linked-list/193/singly-linked-list/739/


三、删除操作 - 单链表 

参考文献:https://leetcode-cn.com/explore/learn/card/linked-list/193/singly-linked-list/740/


四、设计链表

4.1 题目要求


4.2 解决过程

面试要点


4.2.1 单链表

class MyLinkedList:
    def __init__(self):  
        """
        Initialize your data structure here.
        """
        self.size = 0  # 链表长度/元素数目 
        self.head = ListNode(0)  # 作为伪头部的哨兵节点
        # 使用哨兵节点的好处在于, 能够对链表中的所有节点采取相同的处理操作, 而不必过多考虑特殊情况

class ListNode:
    def __init__(self, x):
        self.val = x  
        self.next = None  

# 伪代码
new.next = pred.next  # 新插入节点 new 指向其前一个节点指向的下一个节点 pred.next
pred.next = new  # 新插入节点 new 的前一个节点 pred 指向自己作为下一个节点

例如, 在 index=1 处插入元素为 (5) 的节点, 则需要令 (5) 先指向 (3), 再令 (2) 指向 (5)
反之不可, 如倘若先令 (2) 指向 (5), 则对 (3) 的引用会丢失, 导致断链

例如, 在 index=0 处即头部插入元素为 (5) 的节点, 则需要令 (5) 先指向 (2), 再令 伪头部/哨兵节点 指向 (5)

# 伪代码
# delete pred.next 
pred.next = pred.next.next  # 通过令待删除节点的前驱节点指向待删除节点的下一个节点, 实现删除

例如, 删除 index=1 处的节点 (3), 只需令 (2) 指向 null 忽视/跳过 (3), 从而实现删除

# 伪代码
# index steps needed 
# to move from sentinel node to wanted index
for _ in range(index + 1):
    curr = curr.next
return curr.val

例如, 获取 index=1 处的节点元素, 则遍历链表直至到达, 复杂度 O(n)

官方实现

## 官方基本实现
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class MyLinkedList:
    def __init__(self):
        self.size = 0
        self.head = ListNode(0)  # sentinel node as pseudo-head
        
    def get(self, index: int) -> int:
        # 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: int) -> None:
        self.addAtIndex(0, val)
        
    def addAtTail(self, val: int) -> None:
        self.addAtIndex(self.size, val)
        
    def addAtIndex(self, index: int, val: int) -> None:
        # 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: int) -> None:
        # 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

更优实现

通过一些处理 (__slots__、非公有化、内嵌类和方法整合等),可以得到更好的书写方式:

2020/07/08 - 59.60%

## 个人特别实现
class MyLinkedList:
    """ An implementation of singly linked list """
    
    # -------------------------- nested _ListNode class -------------------------- #
    class _ListNode: 
        """ Lightweight, nonpublic class for storing a singly linked node """
        __slots__ = "_val", "_next"         # straemline memory usage (special) ####
        
        def __init__(self, elem=0, nxt=None):  # initialize node's fields (more versatile)
            self._val = elem                # reference to user's element (nonpublic)
            self._next = nxt                # reference to the next node (nonpublic)
    
    # ---------------------------- linked list methods --------------------------- #
    def __init__(self):                       
        self._size = 0                      # the number of elements (nonpublic)
        self._head = self._ListNode()       # the sentinel node as pseudo-head (nonpublic)
        
    def _findElement(self, index):
        """
        Nonpublic mothod for finding the predecessor of the node with specfic index
        """
        node = self._head                   
        for _ in range(index):
            node = node._next
        return node
        
    def get(self, index: int) -> int:
        """
        Get the value of the index-th node in the linked list. 
        If the index is invalid, return -1.
        """
        if (index < 0) or (index >= self._size):  # if index is invalid, return -1
            return -1

        curr = self._findElement(index+1)   # index steps needed to move from sentinel node to wanted index     
        return curr._val

    def addAtHead(self, val: int) -> None:
        """
        Add a node of value val before the first element of the linked list. 
        After the insertion, the new node will be the first node of the linked list.
        """
        self.addAtIndex(0, val)             # add new node at the position of index=0
        
    def addAtTail(self, val: int) -> None:
        """
        Append a node of value val to the last element of the linked list.
        """
        self.addAtIndex(self._size, val)    # add new node at the position of index=end
        
    def addAtIndex(self, index: int, val: int) -> None:
        """
        Add a node of value val before the index-th node in the linked list. 
        If index equals to the length of linked list, 
        the node will be appended to the end of linked list. 
        If index is greater than the length, the node will not be inserted.
        """
        if index > self._size:              # If index is greater than the length, 
            return                          # the node will not be inserted.
        if index < 0:                       # If index is negative, the node can be inserted at the head
            index = 0                       #  of the list, or reversed to a positive index (bad idea).

        pred = self._findElement(index)     # find predecessor of the node to be added
        new_node = self._ListNode(val)      # initialize the new node with element (val)
        new_node._next = pred._next         # set the new node refers to the predecessor's next node
        pred._next = new_node               # set the predecessor refers to the new node
        self._size += 1                     # increment the node count
        
    def deleteAtIndex(self, index: int) -> None:
        """
        Delete the index-th node in the linked list, if the index is valid.
        """
        if (index < 0) or (index >= self._size):  # if the index is invalid, do nothing
            return
 
        pred = self._findElement(index)     # find predecessor of the node to be deleted
        pred._next = pred._next._next       # delete pred.next by skipping reference of it
        self._size -= 1                     # decrement the node count

复杂度分析

迷惑实现

此外,结果排行中,最快的几种实现方式竟然是这样的 (概括如下):

class MyLinkedList:
    def __init__(self):
        self.linkList= list()  # ???

    def get(self, index: int) -> int:
        if (index<0 or len(self.linkList)<=index):
            return -1
        return self.linkList[index]

    def addAtHead(self, val: int) -> None:
        self.linkList.insert(0,val)

    def addAtTail(self, val: int) -> None:
        self.linkList.append(val)

    def addAtIndex(self, index: int, val: int) -> None:
        if (len(self.linkList)>=index and index>=0):
            self.linkList.insert(index,val)

    def deleteAtIndex(self, index: int) -> None:
        if (len(self.linkList)>index and index>=0):
            self.linkList.pop(index)

在 Python 中,数组 (array) 以列表 (list) 的形式封装和优化,而此处挂羊头卖狗肉 —— 相当于使用数组实现等地位的另一数据结构 —— 链表 (linked list),这除了快还有什么学习的意思?干脆不实现链表了直接用内建列表如何?(人类迷惑行为...)


4.2.2 双链表

class MyLinkedList:
    def __init__(self):
        self.size = 0
        # sentinel nodes as pseudo-head and pseudo-tail
        self.head, self.tail = ListNode(0), ListNode(0) 
        self.head.next = self.tail
        self.tail.prev = self.head

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
        self.prev = None

# 伪代码
new.prev = pred
new.next = succ
pred.next = new
succ.prev = new

# 伪代码
pred.next = succ
succ.prev = pred

# 伪代码
# choose the fastest way: to move from the head or to move from the tail
if index + 1 < self.size - index:
    curr = self.head
    for _ in range(index + 1):
        curr = curr.next
else:
    curr = self.tail
    for _ in range(self.size - index):
        curr = curr.prev

官方实现

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next, self.prev = None, None

class MyLinkedList:
    def __init__(self):
        self.size = 0
        # sentinel nodes as pseudo-head and pseudo-tail
        self.head, self.tail = ListNode(0), ListNode(0) 
        self.head.next = self.tail
        self.tail.prev = self.head
        
    def get(self, index: int) -> int:
        """
        Get the value of the index-th node in the linked list. If the index is invalid, return -1.
        """
        # if index is invalid
        if index < 0 or index >= self.size:
            return -1
        
        # choose the fastest way: to move from the head
        # or to move from the tail
        if index + 1 < self.size - index:
            curr = self.head
            for _ in range(index + 1):
                curr = curr.next
        else:
            curr = self.tail
            for _ in range(self.size - index):
                curr = curr.prev    
                
        return curr.val
            
    def addAtHead(self, val: int) -> None:
        """
        Add a node of value val before the first element of the linked list. 
        After the insertion, the new node will be the first node of the linked list.
        """
        pred, succ = self.head, self.head.next
        
        self.size += 1
        to_add = ListNode(val)
        to_add.prev = pred
        to_add.next = succ
        pred.next = to_add
        succ.prev = to_add
        
    def addAtTail(self, val: int) -> None:
        """
        Append a node of value val to the last element of the linked list.
        """
        succ, pred = self.tail, self.tail.prev
        
        self.size += 1
        to_add = ListNode(val)
        to_add.prev = pred
        to_add.next = succ
        pred.next = to_add
        succ.prev = to_add

    def addAtIndex(self, index: int, val: int) -> None:
        """
        Add a node of value val before the index-th node in the linked list. 
        If index equals to the length of linked list, 
        the node will be appended to the end of linked list. 
        If index is greater than the length, the node will not be inserted.
        """
        # 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
        
        # find predecessor and successor of the node to be added
        if index < self.size - index:
            pred = self.head
            for _ in range(index):
                pred = pred.next
            succ = pred.next
        else:
            succ = self.tail
            for _ in range(self.size - index):
                succ = succ.prev
            pred = succ.prev
        
        # insertion itself
        self.size += 1
        to_add = ListNode(val)
        to_add.prev = pred
        to_add.next = succ
        pred.next = to_add
        succ.prev = to_add

    def deleteAtIndex(self, index: int) -> None:
        """
        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
        
        # find predecessor and successor of the node to be deleted
        if index < self.size - index:
            pred = self.head
            for _ in range(index):
                pred = pred.next
            succ = pred.next.next
        else:
            succ = self.tail
            for _ in range(self.size - index - 1):
                succ = succ.prev
            pred = succ.prev.prev
            
        # delete pred.next 
        self.size -= 1
        pred.next = succ
        succ.prev = pred

更优实现

类似于单链表优化过程,此处也对双链表进行 类内嵌、方法封装、成员非公有化 等优化处理。例如,创建一个轻量级非公有 _ListNode 类,由于该类不会直接暴露给用户,所以被正式定义为非公有的、最终的 MyLinkedList 类的内部嵌套类;各种属性成员和成员函数也被同理被定义为非公有的;为提高内存的利用率,专门定义了 __slots__ 方法,因为一个链表中通常具有若干个节点实例。优化实现如下:

2020/07/08 - 94.35%

## 个人特别实现 (未完)
class MyLinkedList:
    """ An implementation of doubly linked list """
    
    # -------------------------- nested _ListNode class -------------------------- #
    class _ListNode: 
        """ Lightweight, nonpublic class for storing a doubly linked node """
        __slots__ = "_val", "_next", "_prev"    # straemline memory usage (special)
        
        def __init__(self, elem=0, nxt=None, pre=None):  # initialize node's fields
            self._val = elem                    # reference to user's element
            self._next = nxt                    # reference to the next node
            self._prev = pre                    # reference to the previous node
    
    # ------------------------- linked list basic methods ------------------------ #
    def __init__(self):
        """ initialize with five nonpublic properties """
        self._size = 0                          # the number of elements
        self._head = self._ListNode()           # sentinel node as pseudo-head
        self._tail = self._ListNode()           # sentinel node as pseudo-tail
        self._head._next = self._tail           # cross reference  →
        self._tail._prev = self._head           # cross reference  ←
        
    def __len__(self):
        """ return the length of the doubly linked list """
        return self._size
    
    def isEmpty(self):
        """ return if the doubly linked list is empty """
        return self._size == 0

    # ----------------------- linked list nonpublic methods ---------------------- #
    def _findNode(self, left_step, right_step):
        """ find the specified node with the fastest starting point and direction """
        if (left_step) < (right_step):          # choose the fastest way
            curr = self._head                   # close to left, move from the head → 
            for _ in range(left_step):
                curr = curr._next
        else:
            curr = self._tail                   # close to right, move from the tail ← 
            for _ in range(right_step):
                curr = curr._prev
        return curr
        
    def _insertNode(self, val, pred, succ):
        """ insert a specific node with three parameters """
        new_node = self._ListNode(val)          # initialize the new node
        new_node._prev = pred                   # cross reference
        new_node._next = succ
        pred._next = new_node
        succ._prev = new_node
        self._size += 1                         # increment the node count
        
    def _deleteNode(self, pred, succ):
        """ delete a specific node with two parameters """
        pred._next = succ                       # skipping cross reference
        succ._prev = pred
        self._size -= 1                         # decrement the node count
        
    # ------------------------ linked list public methods ------------------------ #
    def get(self, index: int) -> int:
        """
        Get the value of the index-th node in the linked list. 
        If the index is invalid, return -1.
        """
        if (index < 0) or (index >= self._size):  # return -1 if index is invalid
            return -1
        curr = self._findNode(index+1, self._size-index)  # find the specified node
        return curr._val
            
    def addAtHead(self, val: int) -> None:
        """
        Add a node of value val before the first element of the linked list. 
        After the insertion, the new node will be the first node of the linked list.
        """
        pred, succ = self._head, self._head._next  # head node's predecessor and sucessor
        self._insertNode(val, pred, succ)

        
    def addAtTail(self, val: int) -> None:
        """
        Append a node of value val to the last element of the linked list.
        """
        succ, pred = self._tail, self._tail._prev  # tail node's sucessor and predecessor
        self._insertNode(val, pred, succ)

    def addAtIndex(self, index: int, val: int) -> None:
        """
        Add a node of value val before the index-th node in the linked list. 
        If index equals to the length of linked list, 
        the node will be appended to the end of linked list. 
        If index is greater than the length, 
        the node will not be inserted.
        """
        if index > self._size:
            return
        if index < 0:
            index = 0
                                                # find predecessor and successor of the node to be added       
        if index < (self._size-index):          
            pred = self._head                   # close to left, move from the head →  
            for _ in range(index):
                pred = pred._next
            succ = pred._next
        else:
            succ = self._tail                   # close to right, move from the tail ←  
            for _ in range(self._size-index):
                succ = succ._prev
            pred = succ._prev
                                                
        self._insertNode(val, pred, succ)       # insert itself

    def deleteAtIndex(self, index: int) -> None:
        """
        Delete the index-th node in the linked list, if the index is valid.
        """
        if (index < 0) or (index >= self._size):  # if the index is invalid, do nothing
            return
                                                # find predecessor and successor of the node to be deleted
        if index < (self._size-index):  
            pred = self._head                   # close to left, move from the head → 
            for _ in range(index):
                pred = pred._next
            succ = pred._next._next
        else:
            succ = self._tail                   # close to right, move from the tail ← 
            for _ in range(self._size-index-1):
                succ = succ._prev
            pred = succ._prev._prev
        
        self._deleteNode(pred, succ)            # delete the node 

注意,其中还加入了一些其他没用到但有意义的方法。从结果来看,几乎比单链表快 2 倍,可见 含哨兵节点的双链表 及其各种优化是有意义的。此外,还可以增加 反转 (reverse)、遍历 (traverse)、更新 (update)、排序 (sort) 等更多功能。

复杂度分析


参考文献:

https://leetcode-cn.com/explore/learn/card/linked-list/193/singly-linked-list/741/

https://leetcode-cn.com/problems/design-linked-list/solution/she-ji-lian-biao-by-leetcode/

https://www.jb51.net/article/146644.htm

https://www.baidu.com/link?url=OGYmP2uIVFw_L2kTKyQQFhXWB3-mdy8-cXn4aW8B-QEYoPKHogGHC0a1jXhRE252&wd=&eqid=d7968cf8001dc2e4000000065f05f489

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值