文章目录
前言
完成数组部分的练习后,接下来需要实现链表的一些算法题。链表与数组最大的不同在于储存空间的不连续性。每个元素的储存空间包括元素值和元素指向,因此后一个元素的查找需要先访问前一个元素。Python中可用以下代码定义:
class ListNode:
def __init__ (self, val = 0, next = None):
self.val = val
self.next = next
这里涉及到了对象的定义,其中需要注意的是:
(1)init 左右各有两个’_',这时默认的写法,方便识别;
(2)self表示具体的实例,init中定义的是实例的具体属性;
(3)实例属性优先于对象属性
一、移除链表元素
LeetCode第203题:
题目非常的简单,主要的难点在于对链表概念的理解,代码如下:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
# 构件虚拟节点
dummy_head = ListNode()
dummy_head.next = head
cur = dummy_head # 地址操作而已,不会开辟新的空间
while cur.next != None:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return dummy_head.next
需要注意的是代码中的cur = dummy_head,是类似与C++中的指针,不会开辟新的空间,cur的变化会引起dummy_head的变化。
二、设计链表
LeetCode第707题:
这个题目是中等难度题目,涉及到链表的增删改查,像是一系列小的题目的综合,整体难度不大,需要注意的是节点对象与链表对象单独定义。其次,这一个题目对弄明白对象构造挺有帮助,可以经常看看。代码如下:
class ListNode:
def __init__(self, val = 0, next = None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.head = ListNode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index > (self.size - 1):
return -1
cur = self.head
while index:
cur = cur.next
index -= 1
return cur.next.val
def addAtHead(self, val: int) -> None:
new_node = ListNode(val)
new_node.next = self.head.next
self.head.next = new_node
self.size += 1
def addAtTail(self, val: int) -> None:
cur = self.head
i = 0
while i < self.size:
cur = cur.next
i += 1
new_node = ListNode(val)
cur.next = new_node
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index <= 0:
self.addAtHead(val)
return
elif index == self.size:
self.addAtTail(val)
return
elif index > self.size:
return
new_node = ListNode(val)
cur = self.head
i = 0
while i < index:
cur = cur.next
i += 1
new_node.next = cur.next
cur.next = new_node
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
cur = self.head
while index:
cur = cur.next
index -= 1
cur.next = cur.next.next
self.size -= 1
三、反转链表
LeetCode第206题:
该题目有两种解题方法,分别为双指针法和递归法
(1)双指针法:
双指针的思想为,一前一后两个指针,前面的为cur,后面的为pre,二者一直保持相邻状态。在转换位置时,先用temp临时变量存储cur.next,然后将pre赋值给cur.next实现箭头的转变,随后两个指针向前推进,将cur赋值给pre,使得pre前移;将temp赋值给cur使得cur前移。最后当cur为None时终止判断,返回pre。
# 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: Optional[ListNode]) -> Optional[ListNode]:
cur = head # 类似于指针指向头部
pre = None # 空指针
while cur != None:
temp = cur.next # 临时变量存储实际上的cur.next
cur.next = pre # 断开该节点的后向指针,并指向pre
pre = cur # pre指针向前移动
cur = temp # cur指针向前移动
return pre
# pre、cur、head这些都是指针,而整个链表只有一个,所以才能够相互影响
# 最后cur指向None,pre指向最后一个元素,head没变过指向第一个元素
(2)递归
递归的思想与双指针一致。
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse(pre, cur):
if cur == None:
return pre
temp = cur.next
cur.next = pre
return reverse(cur, temp)
return reverse(None, head)
递归的另一种写法的思路与该写法不一样,是由后向前的变化,似乎更加符合递归的套路,未来有机会再仔细学习。
四、两两交换链表中的节点
LeetCode第24题:
完成前面的题目之后该题比较好些,需要的是动手画图来看看换位的过程是怎么一步步实现的,中间分为三个步骤:
(1) cur的下一个节点转移给pre的下一个节点,
(2) pre变为cur的下一个节点,
(3) 前置节点的下一个节点转移给cur
还需要注意的是,这里需要构造一个dummy_node(虚拟节点),方便操作,循环的判断条件即下一个和下下一个节点都不能为空。
代码如下:
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy_head = ListNode()
dummy_head.next = head
mirror = dummy_head # 最后直接返回dummy_head.next就好
while mirror.next != None and mirror.next.next != None: # mirror = mirror.next.next
pre = mirror.next
cur = mirror.next.next
pre.next = cur.next
cur.next = pre
mirror.next = cur
mirror = mirror.next.next
return dummy_head.next
总结
链表的第一部分到这里就结束了,在这个过程中,我们明白了链表的概念,节点的构造、链表的构造,并自己书写了链表增删改查的算法,后面又写了倒叙链表,以及两两交换链表的实现,整体上难度不大,重要的是学会第二题链表的设计,理解了链表每一步的实现后面的都相对来说比较轻松就能解决。