给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
提示:
列表中的节点数目在范围 [0, 104]
内
1 <= Node.val <= 50
0 <= val <= 50
这个题目与之前的删除基础操作类似,可以直接对链表用一个cur_Node节点进行遍历操作,只要找到数据相符的直接跳过即可,但如果用cur_Node这个节点进行遍历,该怎么跳过呢?会出现下图这样的问题:
如上图所示,当cur_Node遍历到需要删除的节点时,只用一个遍历节点的话,想要跳过该节点就十分困难了,那么我们不妨换个思路。利用cur_Node.next进行遍历看看呢:
这样是不是就清晰明了了,我们只需要新建一个虚拟的头节点,让cur_Node从这个虚拟的头节点开始遍历,当cur_Node的下一个节点的值是需要删除的值时,直接跳到next的next即可,上图就是从4跳到6。代码如下:
class Solution(object):
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
dummy = ListNode(0) #虚拟的头节点,以便简化删除操作
dummy.next = head #虚拟头节点指向真正的头节点
cur_Node = dummy #将cur_Node赋值为虚拟头节点
while cur_Node.next: #将cur_Node的下一节点作为判断依据
if cur_Node.next.val == val:
cur_Node.next = cur_Node.next.next
else:
cur_Node = cur_Node.next
return dummy.next #注意,这里返回的是真实的头节点
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val
和 next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList
类:
MyLinkedList()
初始化 MyLinkedList
对象。
int get(int index)
获取链表中下标为 index
的节点的值。如果下标无效,则返回 -1
。
void addAtHead(int val)
将一个值为 val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
void addAtTail(int val)
将一个值为 val
的节点追加到链表中作为链表的最后一个元素。
void addAtIndex(int index, int val)
将一个值为 val
的节点插入到链表中下标为 index
的节点之前。如果 index
等于链表的长度,那么该节点会被追加到链表的末尾。如果 index
比长度更大,该节点将 不会插入 到链表中。
void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为 index
的节点。
示例:
输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
myLinkedList.get(1); // 返回 3
这个题目是个很经典的题,把链表的基本操作全部覆盖了,相当于自己实现一遍链表的基础功能,但我愣是写了一个小时,看来还是基础掌握不牢啊。
第二天又重新写了一次,快了不少,还是注意到有些地方没有理解清楚,下面来分享一下。
这个题可以使用单链表或者双链表来实现,就还是再练练单链表吧。
设计单链表的第一步就是先写一个节点出来,这个节点要有数据域与指针域,明显定义如下:
#定义单链表节点,具有数据域与指针域
class Node:
def __init__(self,val,next):
self.val = val
self.next = next
然后就是初始化链表了,只需要给一个为None的头节点就可以。
class MyLinkedList:
#初始化链表
def __init__(self):
self.head = None
准备工作完成,接下来就可以一个一个实现功能了。
要求如下:
int get(int index)
获取链表中下标为 index
的节点的值。如果下标无效,则返回 -1
。index
从0开始。
这个函数通过遍历就可以实现,而且第二遍写的时候想了想,可以不使用之前的计数器的,直接用index
标记位置就行了。
我很多时候在写循环的时候往往考虑不清楚位置的对应关系,这时候看看边界情况就可以了,如下:
因此,可以利用下标index作为循环条件,当index为0时跳出循环,直接返回当前遍历节点的next即可。
下标无效怎么办?
第一种特殊情况是,链表为空,此时只需要检测self.head
是否为None就行了
#特殊情况,链表为空
if not cur_Node:
return -1
第二种特殊情况是,下标值超过了链表的最大长度,这就需要我们在循环过程中进行检测了,该设置什么作为检测条件呢?继续考虑下边界问题,如下:
能够发现,需要查找的节点恰好不在链表中时,当index=0,遍历节点cur正好落在null上面。那么我们是不是可以推断,如果需要查找的节点超过链表时,即index大于链表长度时,cur在循环遍历中肯定会先一步指到null上面。
因此,我们在遍历过程中可以检测cur是否为None即可。该函数整体代码如下:
#获取下标为index的节点的值,从0开始
def get(self, index: int) -> int:
dummy = Node(0,self.head) #定义虚拟头节点,指向真正的头节点
cur_Node = dummy.next #定义一个指针用于遍历链表,从头节点开始
#特殊情况,链表为空
if not cur_Node:
return -1
while index:
cur_Node = cur_Node.next
index -= 1
#特殊情况,index大于链表长度
if not cur_Node:
return -1
return cur_Node.val
要求:void addAtHead(int val)
将一个值为 val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。就是基础操作中的首部添加节点,直接新设一个数据为val
的节点,指向头节点,然后头节点更新为该新建节点即可。较为简单,直接上代码。
#在首部添加节点
def addAtHead(self, val: int) -> None:
new_Node = Node(val,self.head)
self.head = new_Node
要求:void addAtTail(int val)
将一个值为 val
的节点追加到链表中作为链表的最后一个元素。是尾部添加节点操作,也与之前类似。需要设置一个指针来遍历链表,找到链表的最后一个节点,然后指向新建节点即可。
需要注意一下特殊情况,即链表为空的时候,这时候这届调用一下上面的addAtHead
函数即可。代码如下:
#在尾部添加节点
def addAtTail(self, val: int) -> None:
dummy = Node(0,self.head) #定义虚拟头节点,指向真正的头节点
last_Node = dummy #定义一个指针用于遍历链表,从头节点开始
new_Node = Node(val,None)
if not self.head:
self.addAtHead(val)
while last_Node.next :
last_Node = last_Node.next
last_Node.next = new_Node
要求:void addAtIndex(int index, int val)
将一个值为 val
的节点插入到链表中下标为 index
的节点之前。如果 index
等于链表的长度,那么该节点会被追加到链表的末尾。如果 index
比长度更大,该节点将 不会插入 到链表中。
该函数实现思路是通过遍历找到需要插入节点的位置,然后将前一节点的next指向新建节点,新建节点的next指向后一节点即可,如下:
可以看到,让cur从虚拟头节点开始遍历时,当index为0退出循环的时候,cur正好遍历到下标为index的节点之前,因此可直接用新建节点的next指向当前节点的next,当前节点的next更新为新建节点。尾部插入同理如下:
较为麻烦的是特殊情况的处理:
上图为index超过链表长度的边界情况,可以发现当index=0时,cur指针正好指向了null,我们可以在循环结束后写一个判断cur是否为None的语句即可。还有一种在循环中进行判断的方法就是看cur的next指向哪里,当index大于0的同时,cur的next如果已经指向了null,那就说明了index肯定是超过了链表的长度的。
因此该部分代码可以这样写:
#链表中添加节点
def addAtIndex(self, index: int, val: int) -> None:
new_Node = Node(val,None)
dummy = Node(0,self.head) #定义虚拟头节点
cur_Node = dummy
if index == 0:
self.addAtHead(val)
return
while index:
if index and not cur_Node.next:
return
cur_Node = cur_Node.next
index -= 1
new_Node.next = cur_Node.next
cur_Node.next = new_Node
要求:void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为 index
的节点。
同样画图考虑下各种情况:
即遍历之后让cur的next指向cur的next的next即可。
特殊情况,当index大于链表长度时:
显然,此时的cur的next已经指向了null,由此可推断,当index大于链表长度的所有情况下,cur的next肯定会在循环结束前就已经指向null,因此,代码如下:
#删除某个位置的节点
def deleteAtIndex(self, index: int) -> None:
if index < 0 :
return
dummy = Node(0,self.head)
cur_Node = dummy
if index == 0:
self.head = self.head.next
return
while index:
cur_Node =cur_Node.next
index -= 1
if not cur_Node.next:
return
cur_Node.next = cur_Node.next.next
整体代码如下:
#定义单链表节点,具有数据域与指针域
class Node:
def __init__(self,val,next):
self.val = val
self.next = next
class MyLinkedList:
#初始化链表
def __init__(self):
self.head = None
#获取下标为index的节点的值,从0开始
def get(self, index: int) -> int:
dummy = Node(0,self.head) #定义虚拟头节点,指向真正的头节点
cur_Node = dummy.next #定义一个指针用于遍历链表,从头节点开始
#特殊情况,链表为空
if not cur_Node:
return -1
while index:
cur_Node = cur_Node.next
index -= 1
#特殊情况,index大于链表长度
if not cur_Node:
return -1
return cur_Node.val
#在首部添加节点
def addAtHead(self, val: int) -> None:
new_Node = Node(val,self.head)
self.head = new_Node
#在尾部添加节点
def addAtTail(self, val: int) -> None:
dummy = Node(0,self.head) #定义虚拟头节点,指向真正的头节点
last_Node = dummy #定义一个指针用于遍历链表,从头节点开始
new_Node = Node(val,None)
if not self.head:
self.addAtHead(val)
while last_Node.next :
last_Node = last_Node.next
last_Node.next = new_Node
#链表中添加节点
def addAtIndex(self, index: int, val: int) -> None:
new_Node = Node(val,None)
dummy = Node(0,self.head) #定义虚拟头节点
cur_Node = dummy
if index == 0:
self.addAtHead(val)
return
while index:
if index and not cur_Node.next:
return
cur_Node = cur_Node.next
index -= 1
new_Node.next = cur_Node.next
cur_Node.next = new_Node
#删除某个位置的节点
def deleteAtIndex(self, index: int) -> None:
if index < 0 :
return
dummy = Node(0,self.head)
cur_Node = dummy
if index == 0:
self.head = self.head.next
return
while index:
cur_Node =cur_Node.next
index -= 1
if not cur_Node.next:
return
cur_Node.next = cur_Node.next.next
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)