链表不像数组一样提供天然的 index,想要访问特定元素,就必须遍历链表。
每个节点可以同时指向自己的上一个节点与下一个节点,每个节点包括了 value,prev,next
首尾相连的单链表
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
Insert/Delete | 复杂度 | 使用场景 | |
---|---|---|---|
数组 | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 数据固定,较少删改,较多查询 |
链表 | O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) | 数据易变,较多删改,较少查询 |
注意,插入/删除仅仅指本身的操作,实际中的插入/删除总是需要先通过查询来定位,所以总的复杂度还是 O ( n ) O(n) O(n)。
虚拟头节点的简洁写法:
virtual_head = LinkedNode(next=head) # head is the actual first node
采用虚拟头节点的时候,一定要
return virtual_head.next
!!
题目链接 | 解题思路
对于链表元素进行操作时,第一个 node 通常是一个特殊情况。在这道题中,删除 head node 和别的 node 是不一样的。
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
curr_node = head
temp_head = ListNode() # make a virtual head whose next is the actual head
temp_head.next = head
prev_node = temp_head
while curr_node != None:
if curr_node.val != val: # nothing to skip
prev_node = curr_node
else:
prev_node.next = curr_node.next
curr_node = curr_node.next
return temp_head.next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
virtual_head = ListNode(next=head)
curr_node = virtual_head
while (curr_node.next != None):
if curr_node.next.val == val:
curr_node.next = curr_node.next.next
else:
curr_node = curr_node.next
return virtual_head.next
题目链接 | 解题思路
操作题,考验对链表的基础理解、操作。
class LinkedNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.virtual_head = LinkedNode()
def get(self, index: int) -> int:
count = 0
curr_node = self.virtual_head.next
while (curr_node != None):
if count == index:
return curr_node.val
curr_node = curr_node.next
count += 1
return -1
def addAtHead(self, val: int) -> None:
insert_node = LinkedNode(val=val) # the node of "val" to be inserted
insert_node.next = self.virtual_head.next
self.virtual_head.next = insert_node
def addAtTail(self, val: int) -> None:
curr_node = self.virtual_head
while (curr_node.next != None):
curr_node = curr_node.next
# now curr_node is the last node in the linked list
curr_node.next = LinkedNode(val=val)
def addAtIndex(self, index: int, val: int) -> None:
# here, we can take virtual node as an existing node
# then, we can insert "val" after the index-th node
curr_node = self.virtual_head
count = 0
while (curr_node != None):
if count == index: # insert after curr_node
insert_node = LinkedNode(val=val)
insert_node.next = curr_node.next
curr_node.next = insert_node
curr_node = curr_node.next
count += 1
def deleteAtIndex(self, index: int) -> None:
# here, we can take virtual node as an existing node
# then, we can delete the "next node" when we reach "index"
curr_node = self.virtual_head
count = 0
while (curr_node != None):
if count == index:
if curr_node.next == None:
curr_node.next = None
else:
curr_node.next = curr_node.next.next
curr_node = curr_node.next
count += 1
题目链接 | 解题思路
同样可以使用双指针进行 in-place 的反转!双指针真是无处不在啊。
创建新链表,遍历进行反转,属于基础解法,但是占用了不少空间。
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
virtual_head = ListNode()
curr_node = head
while (curr_node != None):
new_node = ListNode(val=curr_node.val)
new_node.next = virtual_head.next
virtual_head.next = new_node
curr_node = curr_node.next
return virtual_head.next
创建新链表的 recursion,同时还有超高的时间复杂度,不推荐。唯一的优势是比较容易想到。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
# base case: 0 or 1 node
if head == None or head.next == None:
return head
# base case: 2 nodes in the list
if head.next.next == None:
new_head = ListNode(val=head.next.val)
new_head.next = ListNode(val=head.val, next=None)
return new_head
# recursive case: more than 2 nodes
temp_head = self.reverseList(head.next)
curr_node = temp_head
while (curr_node.next != None):
curr_node = curr_node.next
curr_node.next = ListNode(val=head.val)
return temp_head
快指针探路,慢指针保存答案,但是注意在 linked list 中需要额外的变量来保存原本的 next。所以要清楚认识到不同变量的含义。
# iterative reverse
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
prev_node = None # record the previous node in the original linked list
curr_node = head
temp_node = None # temporarily keep the value in the middle
# update prev_node iteratively so it records the final results
while (curr_node != None):
temp_node = curr_node.next # keep the link to next_node
curr_node.next = prev_node
prev_node = curr_node
curr_node = temp_node
return prev_node
比双指针更简单,直接依靠 recursion 处理反转方向,但也对变量含义的理解有更高的要求。注意这个方法的空间复杂度更高,因为每层 recursion 都要创建一个新的中间变量。
# recursive reverse
class Solution:
def reverse(self, results: Optional[ListNode], rest: Optional[ListNode]) -> Optional[ListNode]:
# base case: no more node to reverse
if rest == None:
return results
# recursive
temp_node = rest.next
rest.next = results
return self.reverse(rest, temp_node)
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
return self.reverse(None, head)