203. 移除链表元素
移除链表元素
给你一个链表的头节点 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 #注意,这里返回的是真实的头节点
707. 设计链表
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性: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
这个题目是个很经典的题,把链表的基本操作全部覆盖了,相当于自己实现一遍链表的基础功能,但我愣是写了一个小时,看来还是基础掌握不牢啊。
第二天又重新写了一次,快了不少,还是注意到有些地方没有理解清楚,下面来分享一下。
1.实现功能前的准备
这个题可以使用单链表或者双链表来实现,就还是再练练单链表吧。
设计单链表的第一步就是先写一个节点出来,这个节点要有数据域与指针域,明显定义如下:
#定义单链表节点,具有数据域与指针域
class Node:
def __init__(self,val,next):
self.val = val
self.next = next
然后就是初始化链表了,只需要给一个为None的头节点就可以。
class MyLinkedList:
#初始化链表
def __init__(self):
self.head = None
准备工作完成,接下来就可以一个一个实现功能了。
2.get函数
要求如下:
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
3.addAtHead函数
要求:void addAtHead(int val)
将一个值为 val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。就是基础操作中的首部添加节点,直接新设一个数据为val
的节点,指向头节点,然后头节点更新为该新建节点即可。较为简单,直接上代码。
#在首部添加节点
def addAtHead(self, val: int) -> None:
new_Node = Node(val,self.head)
self.head = new_Node
4.addAtTail函数
要求: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
5.addAtIndex函数
要求: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
6.deleteAtIndex函数
要求: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
7.整体代码
整体代码如下:
#定义单链表节点,具有数据域与指针域
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)