文章目录
数据结构算法相关代码集合见https://github.com/lankuohsing/DataStructureInPython ,欢迎fork和star
众所周知,计算机中的数据结构底层无非是链表(linked list)或者线性表(linear list)。因此,掌握这些基本的数据结构的结构和常用操作是很重要的。本文我们来介绍一下链表的常用操作,主要采用经典算法题的形式来加以呈现说明
0. 链表的数据结构描述
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,也即下一个节点的位置是同过上一个节点的next指针来确定的。由于这个特点,链表有以下几个优点:
- 在创建时不需要知道数据的总大小,因此可以充分利用计算机的内存空间。
- 在对链表进行插入和删除节点的操作时,时间复杂度可以达到O(1)
同时也有以下几个缺点: - 每个节点需要额外存储指针,空间消耗较大
- 查找某个元素的时间复杂度为O(n)
1. 常见操作
1.1 删除链表中的结点
leetcode-237 237. 删除链表中的节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
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
关键点:将该节点后继节点的数据拷贝到当前节点,并且删除后继节点
leetcode-203 203. 移除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
if head is None:
return head
while head is not None and head.val==val:
head=head.next
if head is None:
return head
cur=head
while cur.next is not None:
if cur.next.val==val:
cur.next=cur.next.next
else:
cur=cur.next
return head
关键点:用一个指针cur从头到后遍历,如果该指针的下一个节点非空并且值等于目标值,则cur的next指向下下个节点(也即删除了下个节点),否则cur往后移动。直到cur的下个节点为空(到了末尾)。需要注意的是,可能cur一开始就指向了目标节点(也即head就是目标节点),这时就要移动cur到下一个节点。
leetcode-83 83. 删除排序链表中的重复元素
存在一个按升序排列的链表,给你这个链表的头节点 head
,请你删除所有重复的元素,使每个元素 只出现一次 。
返回同样按升序排列的结果链表。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if head is None:
return head
cur=head
while cur.next is not None:
if cur.val==cur.next.val:
cur.next=cur.next.next
else:
cur=cur.next
return head
关键点:用一个cur从头开始往后遍历,如果cur的后继结点非空,且后继结点的值等于后继的后继的值,则cur指向后继的后继(删除第一个重复的值),否则cur往后移动。知道cur变为空,此时返回head
1.2 合并有序链表
leetcode-21 21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
# 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, l1: ListNode, l2: ListNode) -> ListNode:
if l1 is None:
return l2
if l2 is None:
return l1
start=None
cur=None
if l2.val <= l1.val:
cur=l2
l2=l2.next
else:
cur=l1
l1=l1.next
start=cur
while l1 is not None and l2 is not None:
if l2.val <= l1.val:
cur.next=l2
l2=l2.next
else:
cur.next=l1
l1=l1.next
cur=cur.next
if l1 is not None:
cur.next=l1
else:
cur.next=l2
return start
关键点:l1和l2分别往后逐节点移动并比较大小,利用一个指针cur来指向l1和l2中较小的节点,并将cur往后移动。当l1或l2有一个为None时,迭代停止, 不为空的那个节点开始的尾巴直接连在cur后面。要注意的是,需要一个start节点来指向起点。
1.3. 反转链表
leetcode-206 206. 反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
# 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:
if head is None:
return head
p1=head
p2=head.next
while p2 is not None:
head.next=p2.next
p2.next=p1
p1=p2
p2=head.next
return p1
关键点:用两个指针p1和p2,一前一后,每次迭代时head指向p2的后继结点,p2指向p1,p1移动到p2,p2移动到下一个节点(借助head)。
1.4. 带环的链表
leetcode-141 141. 环形链表
给定一个链表,判断链表中是否有环。
# Definition for singly-linked list.
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
fast=head
while fast is not None:
head=head.next
if head is None:
return False
fast=fast.next
if fast is None:
return False
fast=fast.next
if fast==head:
return True
return False
关键点:两个指针同时从head开始出发,一个指针每次走一步,另一个指针每次走两步,如果会相遇(两个指针相等)则说明有环。注意循环的跳出条件,只要有一个指针为None就应该跳出且返回False
leetcode-142 142. 环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
# 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:
fast=head
slow=head
meet=None
is_meet=False
while slow is not None:
slow=slow.next
if slow is None:
return None
fast=fast.next
if fast is None:
return None
fast=fast.next
if fast is None:
return None
if fast==slow:
meet=slow
is_meet=True
break
if is_meet:
slow=head
fast=meet
while slow!=fast:
slow=slow.next
fast=fast.next
return slow
return None
具体分析过程见本人另一篇博文:https://blog.csdn.net/thuchina/article/details/77808522
2. 链表的应用
2.1. 利用链表构建HashSet
leetcode-705 705. 设计哈希集合
class MyHashSet:
def __init__(self):
"""
Initialize your data structure here.
"""
self.base=769
self.data=[[]]*self.base
def hash(self,key:int,base:int):
return key%base
def add(self, key: int) -> None:
index=self.hash(key,self.base)
for e in self.data[index]:
if e==key:
return
self.data[index].append(key)
return
def remove(self, key: int) -> None:
index=self.hash(key,self.base)
for i in range(0,len(self.data[index])):
if self.data[index][i]==key:
self.data[index].pop(i)
return
def contains(self, key: int) -> bool:
"""
Returns true if this set contains the specified element
"""
index=self.hash(key,self.base)
for i in range(0,len(self.data[index])):
if self.data[index][i]==key:
return True
return False
# Your MyHashSet object will be instantiated and called as such:
# obj = MyHashSet()
# obj.add(key)
# obj.remove(key)
# param_3 = obj.contains(key)
关键点:先设定一个素数作为对输入(key)求模运算的基,以这个素数为长度创建一个数组,数组中每个位置(求模的结果作为序号的位置)存一个链表,链表存的是对素数求模后结果一样的输入值(key)。
2.2.利用链表构建HashMap
leetcode-706 706. 设计哈希映射
class MyHashMap:
def __init__(self):
"""
Initialize your data structure here.
"""
self.base=769
self.data=[[]]*self.base
def hash(self,key:int,base:int):
return key%base
def put(self, key: int, value: int) -> None:
"""
value will always be non-negative.
"""
index=self.hash(key,self.base)
for i in range(0,len(self.data[index])):
if self.data[index][i][0]==key:
self.data[index][i]=(key,value)
return
self.data[index].append((key,value))
def get(self, key: int) -> int:
"""
Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key
"""
index=self.hash(key,self.base)
for i in range(0,len(self.data[index])):
if self.data[index][i][0]==key:
return self.data[index][i][1]
return -1
def remove(self, key: int) -> None:
"""
Removes the mapping of the specified value key if this map contains a mapping for the key
"""
index=self.hash(key,self.base)
for i in range(0,len(self.data[index])):
if self.data[index][i][0]==key:
self.data[index].pop(i)
return
# Your MyHashMap object will be instantiated and called as such:
# obj = MyHashMap()
# obj.put(key,value)
# param_2 = obj.get(key)
# obj.remove(key)
关键点:与前面的HashSet类似,只不过存的是<key,value>。定位仍然是用key来定位