链表的一些基础小知识
链表类型
单链表
双链表
循环链表
链表操作
删除节点
删除某点即将该点上一节点的指针指向该点的下一个节点(C++需要手动释放该节点,其他语言有自己的内存回收机制无需手动释放)
增加节点
增加节点即将改变上一节点的指针指向加入的节点以及加入节点的指针指向当前节点
今日刷题指南
移除链表元素
# Definition for singly-linked list.
class ListNode(object):
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution(object):
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
# 头节点的删除无法直接转换成上一节点指针指向下一节点(头节点无上一节点)
# 创建虚拟头节点,将删除当前节点的过程全都统一为上一节点指针指向下一个节点
dummyhead = ListNode(next=head)
current = dummyhead
# 遍历所有节点,判断当前节点(current.next.val)是否需要删除,需要知道上一节点的指针(current.next)
while current.next:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
return dummyhead.next # 返回虚拟头节点指向的链表
一开始拿到这道题思路还是清晰的,删除当前节点等同于将上一节点指针指向下一节点,然后构造一个虚拟头节点指向头节点。但是,一敲代码人傻眼。
首先,需要明确知道的是链表的定义,如ListNode类定义那样,每一个节点包含两个属性:val(值)以及next(指向下一节点的指针)。
其次,如何遍历所有节点并且对比目标值哪些节点需要删除:
- 起始节点: 起始节点current即为构造的虚拟头节点,其值定义为0,指向head节点;
- 遍历条件: 由于删除某一节点时,我们需要知道其上一节点的指针,因此遍历条件定为current.next不为空;
- 上一节点:current,当前节点:current.next,当前节点值:current.next.val;
- 判断条件:current.next.val是否为指定值,是的话将上一节点指针current.next–>下一节点current.next.next,否则遍历至下一节点:current = current.next。
设计链表
复盘这道题目,需要深呼吸十秒钟。
将链表的各种操作封装成函数
class ListNode:
def __init__(self,val=0,next=None):
self.val = val
self.next = next
class MyLinkedList(object):
def __init__(self):
self.dummy_head = ListNode()
self.size = 0
def get(self, index): # 获取链表中下标为index的节点值(如果无效返回-1)
"""
:type index: int
:rtype: int
"""
if index < 0 or index >= self.size:
return -1
current = self.dummy_head.next
for i in range(index):
current = current.next
return current.val
def addAtHead(self, val): #将值为val的节点插入链表第一个元素前成为头节点
"""
:type val: int
:rtype: None
"""
self.dummy_head.next = ListNode(val=val,next=self.dummy_head.next)
self.size += 1
def addAtTail(self, val): #将值为val的节点追加为链表最后一个节点
"""
:type val: int
:rtype: None
"""
current = self.dummy_head
while current.next:
current = current.next
current.next = ListNode(val=val)
self.size += 1
def addAtIndex(self, index, val): #将值为val的节点插入链表中下标为index的节点之前
"""
:type index: int
:type val: int
:rtype: None
"""
if index < 0 or index>self.size: #index不合法或者比链表长度大,该节点不会插入到链表中
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = ListNode(val,next=current.next)
self.size += 1
def deleteAtIndex(self, index): #删除链表中下标为index的节点
"""
:type index: int
:rtype: None
"""
if index >= 0 and index < self.size:
current = self.dummy_head
for i in range(index):
current = current.next
current.next = current.next.next
self.size -= 1
总结出来的规律就是:
- 明确当前节点需要是头节点还是虚拟头节点
- 最好画个示意图,演示一下遍历过程
反转链表
# 解法一:双指针法
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
current = head
pre = None
while current:
tmp = current.next #临时保存下一节点(因为接下来要改变current.next)
current.next = pre #反转
# 更新pre、current指针
pre = current
current = tmp
return pre
# 解法二:递归法
class Solution:
def reverseList(self, head):
return self.reverse(head, None)
def reverse(self, cur, pre): # 递归函数
if cur == None:
return pre
temp = cur.next
cur.next = pre
return self.reverse(temp, cur)
我自己在做的时候,完全没有想到说用双指针,反而想着加一个虚拟头节点,但是总是会报错提示none类型无法获取next指针,感觉是加上虚拟头节点之后遍历条件的问题导致。
用双指针瞬间逻辑就比较通畅了,重点就是需要明确遍历条件、双指针的初始化以及变化过程。
本来说好晚上把这部分弄完的,赖到现在,凌晨5点14分。莽人就是晚上说好要完成但没完成的事情,通宵都要给他办咯。