LeetCode 203.移除链表元素:
看到题目后的想法
- 添加虚拟头节点,通过虚拟头节点遍历和删除链表节点。优点在于不需要将头节点的值进行单独判断,代码逻辑一致。
需要注意的是:采用pre遍历删除链表节点时,循环判断条件为pre.next,所以删除节点后不需要更新pre
class Solution(object):
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
new_head = ListNode(next = head) # 添加虚拟头结点
pre = new_head
while pre.next != None:
if pre.next.val == val:
pre.next = pre.next.next # 删除节点之后不需要迭代pre,因为pre.next被更新了
else:
pre = pre.next
return new_head.next
- 在原始的链表上进行判断,同时将头结点单独判断。首先需要单独判断头节点,当头节点值不为 val 时遍历删除后续节点。需要注意的是,对头结点的删除操作需要使用循环,因为可能出现如下情形。
class Solution(object):
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
# 不添加虚拟头节点删除链表元素
while head != None and head.val == val: # 循环删除值为val的头节点
head = head.next
if head == None: # 删除后链表为空
return head
# 此时head.val != val
pre = head
while pre.next != None:
if pre.next.val == val:
pre.next = pre.next.next
else:
pre = pre.next
return head
- 采用递归的方式删除链表的节点。分为三类,
① 头节点为空,直接返回头节点
② 头节点值为 val,将head.next链表删除节点后的头节点作为新的头节点
③ 头节点值不为val,将head.next链表删除节点后的头节点链接到head之后
class Solution(object):
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
# 空链表
if head == None:
return None
# 递归处理
elif head.val == val:
new_head = self.removeElements(head.next, val) # 类中调用对应的函数用self.removeElements
head = new_head
return head
else:
head.next = self.removeElements(head.next, val)
return head
遇到的问题
- 对于python的链表实现不够熟悉。
# LeetCode上的链表定义
class ListNode(object):
def __init__(self, val = 0, next = None):
self.val = val
self.next = next
# 创建新节点
nodep1 = ListNode() # 创建了一个节点nodep1, 值val = 0, next = None
nodep2 = ListNode(next = head) # nodep2节点, val = 0, next = head
- python的类中调用对应的函数用 self.function
LeetCode 707.设计链表:
看到题目的想法:
1.单链表实现
① 题目所给的类为链表,所以需要先声明一个链表节点的类
class LinkNode(object):
def __init__(self, val = 0, next = None):
self.val = val
self.next = next
② 采用虚拟头节点来初始化链表。同时使用了size记录链表中的节点个数
def __init__(self):
self.dummy_head = LinkNode()
self.size = 0
③. 具体的类中的函数实现。需要注意的是
1)插入或删除节点时,需要同时修改 self.size。
2)获取下标为n的节点,其中n从0开始。
def get(self, index):
"""
:type index: int
:rtype: int
"""
if index < 0 or index > (self.size - 1):
return -1
p = self.dummy_head.next
for i in range(index): # 可以带入index = 0的时候确定range中的是index还是多少
p = p.next
return p.val
def addAtHead(self, val):
"""
:type val: int
:rtype: None
"""
self.size += 1
self.dummy_head.next = ListNode(val = val, next = self.dummy_head.next)
def addAtTail(self, val):
"""
:type val: int
:rtype: None
"""
self.size += 1
p = self.dummy_head
while p.next != None:
p = p.next
p.next = ListNode(val = val)
def addAtIndex(self, index, val):
"""
:type index: int
:type val: int
:rtype: None
"""
if index < 0 or index > self.size: # 插入元素的下标范围为[0, self.size]
return
self.size += 1
pre = self.dummy_head
for i in range(index):
pre = pre.next
pre.next = ListNode(val = val, next = pre.next)
def deleteAtIndex(self, index):
"""
:type index: int
:rtype: None
"""
if index < 0 or index > (self.size - 1):
return
self.size -= 1
pre = self.dummy_head
for i in range(index): # 代入index为0时,确定range中的是index
pre = pre.next
pre.next = pre.next.next
2.循环双链表链表实现
对于不熟悉的人来说很容易在prev上弄错,插入和删除节点还是自己先在纸上写一遍再用代码实现。还是用的创建虚拟头节点的方式实现的。
class DListNode(object):
def __init__(self, val=0, next=None, prev=None):
self.val = val
self.next = next
self.prev = prev
class MyLinkedList(object):
def __init__(self):
self.dummy_head = DListNode()
self.dummy_head.next = self.dummy_head.prev = self.dummy_head
self.size = 0
def get(self, index):
"""
:type index: int
:rtype: int
"""
if index < 0 or index > (self.size - 1):
return -1
cur = self.dummy_head
if index <= (self.size - 1) // 2: # 前半段
for i in range(index + 1):
cur = cur.next
else: # 后半段
for i in range(self.size - index):
cur = cur.prev
return cur.val
def addAtHead(self, val):
"""
:type val: int
:rtype: None
"""
self.size += 1
hnext = self.dummy_head.next # 获取当前头节点的下一个节点
newnode = DListNode(val=val, prev=self.dummy_head, next=hnext) # 新节点
self.dummy_head.next = newnode
hnext.prev = newnode
def addAtTail(self, val):
"""
:type val: int
:rtype: None
"""
self.size += 1
tail = self.dummy_head.prev # 获取尾节点
newnode = DListNode(val=val, prev=tail, next=self.dummy_head)
tail.next = newnode
self.dummy_head.prev = newnode
def addAtIndex(self, index, val):
"""
:type index: int
:type val: int
:rtype: None
"""
if index < 0 or index > self.size:
return
cur = self.dummy_head
if index <= (self.size - 1) // 2: # index在前半段
for i in range(index):
cur = cur.next # 获取目标位置的前一个节点
cnext = cur.next # 目标位置的节点
newnode = DListNode(val=val, prev=cur, next=cnext)
cur.next = newnode
cnext.prev = newnode
else:
for i in range(self.size - index):
cur = cur.prev # 移动到目标节点
cprev = cur.prev # 目标节点的前一个节点
newnode = DListNode(val=val, prev=cprev, next=cur)
cprev.next = newnode
cur.prev = newnode
self.size += 1
def deleteAtIndex(self, index):
"""
:type index: int
:rtype: None
"""
if index < 0 or index > (self.size - 1):
return
cur = self.dummy_head
if index <= (self.size - 1) // 2:
for i in range(index):
cur = cur.next # 找到待删除节点的前一个节点
next = cur.next.next # 目标位置的下一个节点
cur.next = next
next.prev = cur
else:
for i in range(self.size - index):
cur = cur.prev # 找到待删除节点
prev = cur.prev # 待删除节点的前一个节点
prev.next = cur.next
cur.next.prev = prev
self.size -= 1
实现过程中遇到的困难:
① 因为对于循环双链表的节点插入删除,以及从后向前找不熟悉,导致细节出现错误。
② 学会正确的debug方式,对于出现错误的例子,在自己IDE上进行debug。对于较为复杂的错例,使用批处理的方式在本地IDE上进行处理,同时打印出运行过程中的数据,进而一步步准确定位到错误位置。
# 记录一下本次的debug代码
def printList(self): # 类中遍历打印链表
pnext = self.dummy_head.next
prev = self.dummy_head.prev
for i in range(self.size):
print(str(i) + ":" + str(pnext.val), end=' ')
pnext = pnext.next
print('\n')
for i in range(self.size):
print(str(self.size - 1 - i) + ":" + str(prev.val), end=' ')
prev = prev.prev
print('\n')
# 主函数对于复杂的输入案例进行批处理
i = 0
# list27 = []
for func_name, inputs in zip(list1, list2):
if i == 96: # 这个是前面打印输出后找到了错误操作的结果,能直接在错误的位置进行debug
func = getattr(obj, func_name) # 获取函数对象
re = func(*inputs) # 调用函数
else:
func = getattr(obj, func_name) # 获取函数对象
re = func(*inputs) # 调用函数
# if inputs[0] == 27: # 统计操作数为27的操作前一共有多少操作
# list27.append(i)
print(func_name + " " + str(inputs))
obj.printList()
if re is not None:
print(re)
i += 1
# for list3 in list27:
# print(list3) # 找到相同的错误函数调用前面有多少正常的函数调用(因为例子中的操作太多)
LeetCode 206.反转链表:
看到题目后的思路:
- 头插法建立新链表同时删除原链表并返回新链表。但是比较浪费空间
- 在原链表的基础上进行反转
① 对链表中节点的反转:p.next = pre,但是直接反转后原链表丢失,因此在反转前pnext = p.next保存原链表
② 反转后链表的头节点为原链表的尾节点
# 双指针写法
class Solution(object):
def reverseList(self, head):
pre, p = None, head
while p != None:
pnext = p.next # 存储p在原链表中的下一个节点
p.next = pre # 反转
pre = p # 更新p和pre
p = pnext
return pre
# 从前往后递归写法
class Solution(object):
def reverseList(self, head):
return self.reverseNode(None, head)
def reverseNode(self, pre, p):
if p == None:
return pre # 为空时返回pre作为头指针
pnext = p.next
p.next = pre
return self.reverseNode(p, pnext) # 这里也要返回
# 从后往前递归写法
# 首先反转从第二个节点开始的链表,再反转第二个节点和第一个节点
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if head == None or head.next == None:
return head
last = self.reverseList(head.next) # 递归调用,反转从第二个节点开始的链表
head.next.next = head # 反转第二个节点
head.next = None
return last # last为原链表的尾节点也即反转链表的头节点
学习收获:
- 自己实现了循环双链表的各种操作,但是还是不是很熟悉,后续二刷三刷时要多练习
- 对debug的进一步掌握,通过打印输出来一步步找到错误的位置
- 复习了python的一些语法