链表
基本信息:
- 通过指针将一组零散的内存块串联起来(就像是排队,你知道你后面的人是谁)
- 结点(Value):内存块 头结点, 尾结点
- 后继指针(next): 记录下个结点地址的指针
特点:
- 插入快,只需要考虑相邻接点的指针改变。O(1)
- 随机访问慢,因为要一个个遍历结点, 无法通过偏移量查找数据。O(n)
时间复杂度
Lookup O(n)
Insert O(1)
Delete O(1)
Append O(1)
Prepend O(1)
空间复杂度
O(n)
Code eg:
Java LinkedList
单链表
尾结点指向null
循环链表
尾结点指向头结点 从链尾到链头比较方便,需要特殊的环形结构时就可以使用
双链表
更占空间,空间换时间
每个结点同时拥有前驱结点和后继结点
删除插入更快
如果是有序列表,那么按值查找的速度也更快,可以记录一个位置p,每次查询时看看查找值与p的大小,只要查找一半的数据
保护结点
在进行数组操作时我们要注意数组越界,而在进行链表操作时,我们要注意空值判断,我们可以针对单双链表设置保护结点来减少判断。
- 空单链表: 构造一个头结点,next指向null
- 空双链表: 构造头尾结点,头结点next指向尾结点,pre指向null,尾结点pre指向头结点,next指向null。
实战
1.反转链表
leetcode.cn/problems/re…
思路
- 遍历链表,边界值考虑: node是否为null
- 记录两个结点,当前结点head和上一个结点last
- 将当前结点指向上一个结点
- 当前结点和上一个结点同时前进一位,继续反转
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
last = None
while head:
# 记录当前节点的下一个节点
next_head = head.next
# 当前结点指向前一个结点
head.next = last
#当前结点和前一个结点同时往后移动一位
last = head
head = next_head
return last
2.K个一组翻转链表
leetcode.cn/problems/re…
思路
- 分组遍历,往后走k-1步找到一组,记录一组的end
- 每组内部反转,边界判断:head != end.next
- 更新每组跟前一组和后一组的边
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
#返回k-1步后的结点
#返回None说明不足k
def get_end(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
while head:
k -= 1
#往后走k步
if k == 0:
return head
head = head.next
return None
#每组内部反转
def reverse_list(self, head: Optional[ListNode], stop: Optional[ListNode]) :
last = head
#直接从head.next开始, 内部反转
head = head.next
while head != stop:
next_head = head.next
head.next = last
last = head
head = next_head
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
#定义一个保护结点,也就是访问入口,指向head
protect = ListNode(0, head)
#将第一组赋值给上一组
last = protect
while head:
#分组, 获取每组的end结点
end = self.get_end(head, k)
if end == None:
break
next_group_head = end.next
#每组之间的反转
self.reverse_list(head, next_group_head)
#更新当前组和前一组,后一组的边
last.next = end
head.next = next_group_head
#上一组和当前组同时移动k步长
last = head
head = next_group_head
return protect.next
3.邻值查找
www.acwing.com/problem/con…
4.环形链表
leetcode.cn/problems/li…
思路
1.哈希表
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
unique_table = set()
while head:
if head in unique_table:
return True
unique_table.add(head)
head = head.next
return False
2.快慢指针 O(m)时间复杂度
如果有环必相遇,无环快指针走到None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
fast = slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if slow == fast:
return True
return False
5.环形链表2
leetcode.cn/problems/li…
思路
快慢指针
- 首先判断是否有环
- 有环则慢指针到入环点的距离等于head到入环点的距离
- 同时让head和slow移动,必定在起始点相遇
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow = fast = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
while head != slow:
head = head.next
slow = slow.next
return head
return None
6.合并两个有序链表
leetcode.cn/problems/me…
思路
- 终止条件:其中一个链表为空时,返回另一个链表
- 判断 l1 和 l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
if not list1: return list2 # 终止条件,有一个链表为空
if not list2: return list1
if list1.val <= list2.val: # 递归调用
#谁小,将小的结点指向后续需要排序的列表
list1.next = self.mergeTwoLists(list1.next,list2)
return list1
else:
list2.next = self.mergeTwoLists(list1,list2.next)
return list2
思路
- 末位无进位,则末位加一即可,因为末位无进位,前面也不可能产生进位,比如 45 => 46
- 末位有进位,在中间位置进位停止,则需要找到进位的典型标志,即为当前位 %10后为 0,则前一位加 1,直到不为 0 为止,比如 499 => 500
- 末位有进位,并且一直进位到最前方导致结果多出一位,对于这种情况,需要在第 2 种情况遍历结束的基础上,进行单独处理,比如 999 => 1000
class Solution:
def plusOne(self, digits: List[int]) -> List[int]:
n = len(digits)
#逆序遍历
for i in range(n - 1, -1, -1):
digits[i] += 1
#无进位情况,直接返回
if digits[i] < 10:
return digits
else:
#有进位,对10取余
digits[i] %= 10
#进位
return [1] + digits