代码随想录二刷(链表章节)
链表就是通过指针串联在一起的线性结构,每个节点都是由一个数据域和指针域(存放下一个节点的指针)。
双链表就是每个节点中既有指向前一个节点的,也有指向后一个节点的。
循环链表就是把头和尾连起来。
性能分析如下:
下面来看下链表的具体题目:
Leetcode203
这里首先要明白处理链表一个技巧就是可以创立一个哨兵节点。这样就能避免处理一些特殊情况,比如只有头的情况下。这样的话,链表始终是有一个元素的。具体来说过程如下图
# @lc code=start
# 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 = ListNode(next = head)
cur = dummy
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return dummy.next
Leetcode707
具体程序如下:
class Listnode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy = Listnode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
cur = self.dummy
while cur.next:
if index == 0:
return cur.next.val
cur = cur.next
index -= 1
def addAtHead(self, val: int) -> None:
cur = self.dummy
cur.next = Listnode(val,cur.next)
self.size += 1
def addAtTail(self, val: int) -> None:
cur = self.dummy
while cur.next:
cur = cur.next
cur.next = Listnode(val)
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
cur = self.dummy
if index > self.size or index < 0:
return
for _ in range(index):
cur = cur.next
cur.next = Listnode(val, cur.next)
self.size += 1
def deleteAtIndex(self, index: int) -> None:
cur = self.dummy
if index >= self.size or index < 0:
return
for _ in range(index):
cur = cur.next
cur.next = cur.next.next
self.size -= 1
首先分析get函数的实现:
由于题目要求下标是从0开始,所以要用个self.size记录下链表中元素的数量。那么插入头部和插入尾部以及删除指定Index,都会涉及到self.size的变化。
首先要把下标无效的情况给排除掉。也就是
if index < 0 or index >= self.size:
return -1
接下里操作链表的话都是要用哨兵节点(这样就可以将只有一个元素的特殊情况也给合并了)。用for循环遍历就可以:
cur = self.dummy
for _ in range(index):
cur = cur.next
return cur.next.val
插入到第一个元素之前,也就是插入到头之前。这时候就体会到哨兵节点的好用了。
cur.next = Listnode(val,cur.next)
cur代表哨兵节点,哨兵节点的下一个也就是头部的位置,所以创建一个值为val,并且这个节点为原来节点的下一个节点。这样就完成插入的操作了
删除操作的话 ,
所以还要先判断下标是否超范围:
if index >= self.size or index < 0:
return
然后接着for循环去根据index来。然后要删除的节点。
for _ in range(index):
cur = cur.next
所以删除:cur.next = cur.next.next
如果不明白的话。画个图就好理解下,做关于链表的题目。
插入操作的也可以画个图:
cur = self.dummy
if index > self.size or index < 0:
return
for _ in range(index):
cur = cur.next
cur.next = Listnode(val, cur.next)
下面来看下反转链表的题目。
这里采用双指针的思想,pre为该节点要指向的新位置,cur为当前要处理的节点。并且当前要处理的节点的下个位置的信息由于你改变了指向会丢失,所以要用临时变量temp给保存起来。这些操作完成后,pre移动到cur,然后cur移动到下个位置也就是pre的位置。
所以程序思路其实知道了双指针后,并不是很难写出来:
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur = head
pre = None
while cur:
temp = cur.next
cur.next = pre
pre = cur
cur = temp
return pre
Leetcode24
下面来看下下道题两两交换的链表题目:
这个交换链表的也建议是画图的话更好去理解。
看着这个图去写程序就可以。
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy_head = ListNode(next = head)
cur = dummy_head
pre = None
while cur.next and cur.next.next:
temp1 = cur.next
temp2 = cur.next.next.next
cur.next = cur.next.next
cur.next.next = temp1
cur.next.next.next = temp2
cur = cur.next.next
return dummy_head.next
但是while循环要注意不止要判断Cur.next,还要判断cur.next.next。因为程序里面都是两两交换的。
Leetcode19
继续一道倒着删除链表的题目:
这道题思路是用双指针的思路,因为要删除倒数第n个节点。而链表的话只能按顺序遍历,所以用一个快指针,一个慢指针。快指针先走n步,然后在快指针的基础上慢指针移动,终止条件是快指针到达末尾。这样慢指针停的位置就是刚好要删除节点的前面一个。
具体原理如下:
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy_head = ListNode(0,head)
slow,fast = dummy_head,dummy_head
for _ in range(n+1):
fast = fast.next
while fast:
slow = slow.next
fast = fast.next
slow.next=slow.next.next
return dummy_head.next