目录
一、简介 - 单链表
// 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/