链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
https://leetcode-cn.com/tag/linked-list/
链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。
题型分类
聊过具体链表原理之后,我们要总结一下leetcode上面的关于链表的算法题,leetcode上边的题主要分两部分,一部分是本身的题,另一部分是来自剑指offer的题。在这里我们就不区分,只是根据题做下归类及解题套路梳理。
1.单链表处理问题
(1)O(1)时间删除指定结点
(2)链表的倒序打印(递归思路)
(3)倒数第n个结点(快慢指针思想)
(4)链表的翻转(全部和局部)
2.有环形链表处理
(1)判断是否有环
(2)判断环的入口
(3)判断环的长度
3.双链表关系问题
(1)两个链表是否有交叉
(2)交叉的具体位置
(3)公共串的长度等
核心技巧
1.添加哑巴节点(dummy node),减少不必要的判断
2.快慢指针及变形
a.固定步伐的两个指针
b.提前行走多少步的指针
3.环的循环利用
4.链条的递归
5.地址指针的妙用
典型例题及代码
1.O(1)时间复杂度删除指定结点
实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。时间复杂度O(1)。leetcode
class Solution(object):
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
node.val = node.next.val
node.next = node.next.next
扩展问题
链表的查找?
2.快慢指针链表遍历
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。leetcode
class Solution(object):
def getKthFromEnd(self, head, k):
"""
:type head: ListNode
:type k: int
:rtype: ListNode
"""
# 快慢指针 链表遍历
fast = 0
fast_step = head
slow_step = head
while fast_step:
fast += 1
fast_step = fast_step.next
if fast>k:
slow_step = slow_step.next
return slow_step
扩展问题
快慢指针,可以先走多少步,也可以相同步伐;在链表中应用场景非常多。
3.链表的反转
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。leetcode
class Solution(object):
# 迭代双指针反转
def _reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
cur = head
pre = None
post = None
while cur:
post = cur.next
cur.next = pre
pre = cur
cur = post
return pre
# 递归反转
def reverseList(self, head):
if not head or not head.next:
return head
node = self.reverseList(head.next)
head.next.next = head
head.next = None
return node
扩展问题
以上两种反转方法的空间复杂度分别是多少?
4.环形链表问题
判断是否是环形链表,并确定环的开始位置。leetcode
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if not fast:
return None
if fast == slow:
slow = head
while slow !=fast:
slow = slow.next
fast = fast.next
return slow
return None
扩展问题
获取环的长度?
5.交叉链表或公共链表问题
找到两个单链表相交的起始节点。leetcode
class Solution(object):
def getIntersectionNode(self, headA, headB):
"""
:type head1, head1: ListNode
:rtype: ListNode
"""
tmpA = headA
tmpB = headB
count = 1
while tmpA!=tmpB and count<=2:
if tmpA and tmpA.next:
tmpA = tmpA.next
else:
tmpA = headB
count += 1
if tmpB and tmpB.next:
tmpB = tmpB.next
else:
tmpB = headA
if count <=2:
return tmpA
else:
return None
扩展问题
获取公共串的长度?
6.哑结点的使用
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。leetcode
class Solution(object):
def _swapPairs(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
dummy = ListNode(-1)
dummy.next = head
prev_node = dummy
while head and head.next:
# 标记交换两节点
first_node = head;
second_node = head.next;
# 交换操作
prev_node.next = second_node
first_node.next = second_node.next
second_node.next = first_node
# 起始位置重置
prev_node = first_node
head = first_node.next
# 返回头结点
return dummy.next
def swapPairs(self, head):
# 递归返回的标记
if not head or not head.next:
return head
# 待调换的两个节点
first_node = head
second_node = head.next
# 交换
first_node.next = self.swapPairs(second_node.next)
second_node.next = first_node
# 返回
return second_node
扩展问题
无哑结点要怎么做?有的面试要求不得引入哑结点。
7.链表的设计
设计链表的实现。leetcode
class ListNode:
def __init__(self, x):
self.val = x
self.next, self.prev = None, None
class MyLinkedList:
def __init__(self):
self.size = 0
# 双向链表
self.head, self.tail = ListNode(0), ListNode(0)
self.head.next = self.tail
self.tail.prev = self.head
def get(self, index):
"""
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
# 根据链表大小和index,选择从头开始遍历还是从尾部开始遍历
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):
"""
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):
"""
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, val):
"""
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):
"""
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
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
扩展问题
单双链表在java或python中哪些容器中有应用?