写代码的第三天
链表的定义:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
203.移除链表元素
第一想法
链表中删除元素,前一个结点的指针直接指向后面的后面的节点。
第一版错误代码:
想法是从头节点开始向下遍历,遇到等于val的就跳过指向下一个。
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
while head:
head = head.next
if head.val == val:
head = head.next.next
return head
报错:有的节点的next已经=None了,没有继续的next了。
出问题的地方是找到值和目标值相等的结点之后,但是这个结点的前一个节点该怎么找,也不是双向链表。
第二想法
在原链表上修改
解决问题1:在原链表上修改主要问题也是该怎么表示值一样的结点的前一个节点。head头结点是不动的,因为head头结点是要返回链表的,如果head被操作使用的话,它就被改变了,此时如果返回head就不代表这个链表了,所以设置一个指针变量cur,用cur指针来找值一样结点的前一个节点。因为不是双向链表,所有没有cur.pre这种指向前一个结点的指针,所以一定要保证cur是在值一样的结点的前一个节点,这样cur.next就代表的是与目标值一样的结点,这样删除结点就可用cur.next = cur.next.next。
第一版部分代码:
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
cur
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return head
解决问题2:cur初始化。究竟是初始化为head还是head.next?如果初始化为head.next,并且要删除的结点就是head.next就无法找到cur前面的结点了,回到了第一个问题,所以初始化为head。
第二版部分代码:
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
cur = head
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return head
解决问题3:cur指针要向下循环才能找到相应结点,循环条件怎么设置?接下来要操作的是cur.next所以如果cur为空,那么cur.next就报错了,同时要找到cur.next.val的值,所以cur.next也不为空。
为什么return head是因为cur我理解的是它只是定义的一个循环变量,head头结点是一直没变过的,不管什么时候输出head它代表的都是这个链表。(不知道对不对,如有问题欢迎指正)。
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
cur = head
while cur != None and cur.next != None:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return head
看起来好像对了,但是这种情况会有问题:
解决问题4:如果头结点就是与val相等的结点呢?这个时候就把头结点向后指向一个就可以了,head=head.next。
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
if head.val == val:
head = head.next
cur = head
while cur != None and cur.next != None:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return head
报错信息:
原因是没考虑到head有可能是空,空的话是没有val的。
修改后:
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
if head != None and head.val == val:
head = head.next
cur = head
while cur != None and cur.next != None:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return head
看起来有点对了其实还是不对哈哈哈哈。
用if来判断只能解决一次头结点为val的情况,但是测试用例中都是和val相等的值,所以移动一次头结点之后下一个头结点的位置的值还是val,所以应该用while。
正确代码:
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
while head != None and head.val == val:
head = head.next
cur = head
while cur != None and cur.next != None:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return head
啊~~我是个废物~~~~脑子是一点不长~~~~
加虚拟结点修改
解决问题1:为了把上面方法的两种情况(删除头结点和普通结点)用一种方式解决,设置一个虚拟结点在head结点之前,但是需要注意的是,设置的这个结点仅仅是一个结点,它不是循环变量诶。核心问题依旧是找到值相同的结点了,但是该怎么表示值一样的结点的前一个节点,和上面一样。
解决问题2:return的应该是head吗?在设置虚拟结点之后,虚拟结点已经变成了头结点,同时原始的head已经变成了一个普通结点,所以应该是虚拟结点之后的那个结点作为头结点进行输出,return dummyhead.next。
解决问题3:cur应该赋值为dummyhead还是dummyhead.next?同上。
解决问题4:循环条件如何设置?同上。
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
dummyhead = ListNode(val=0,next=head)
cur = dummyhead
while cur != None and cur.next != None:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return dummyhead.next
这段代码中cur != None加不加都不影响结果,原因是cur.next != None已经判断了,并且这个链表就算原始是空链表,我们也新建了一个新结点,所以一定不为空,所以可以不加这个判断。
但是第一种方法是必须加的,我的理解是没有判断head为空的情况,所以head是有可能为空的,cur=head,就很有可能cur也是空,所以要判断cur != None。(个人理解,有问题欢迎指正)。
遇到的问题
cur的初始值究竟设置是多少,这个看了视频才明白,以前都是随便设置错了再试,哎。。。。
707.设计链表
第一想法
链表的基本操作,头插法,尾插法,获取第n个结点,删除第n个结点,在第n个结点之前插入结点。
我的脑子知道怎么写,可我的手写不出来一点。。。。
第二想法
解决问题1:__init__这个里面写什么?(我把结点的结构写进去了哈哈哈哈错的离谱)。需要注意的是这个class只是链表的五个操作,它不包括链表的结构,链表结构要单独写。(开篇那几行)
解决问题2:def init(self):这个方法里面应该写什么?用于初始化类的实例。在这个方法中,你可以设置类的属性或执行其他必要的初始化操作。我理解的是设置一下下面其他的方法中都能用到的一些必要的参数等等。针对下面的五个方法,都需要的是一个虚拟头结点dummyhead,所以在init方法中进行初始化。
错误第一版:
def __init__(self,head):
dummyhead = ListNode(val,next)
dummyhead.next = head
应该是self.dummyhead-XXX,这个self不能丢;初始化类ListNode(val,next)里面的val和next应该是具体的值,如果没有具体的值应该写成ListNode(),在力扣的测试中,创建obj = MyLinkedList()的时候,并没有在MyLinkedList()的括号中传入任何值,所以在MyLinkedList类的__init__方法中不要传任何参数。并且在题目中并没有出现head头结点的样式,所以虽然想表达这个虚拟结点在头结点之前,但是在这个方法中只是设置一个结点而已,并没有链表传入self.dummyhead.next = head这行代码无效。
修改后:
def __init__(self):
self.dummyhead = ListNode()
获取第n个结点
解决问题1:n是从0开始的,因为方法中的index是从0开始的。
解决问题2:需要指针一步一步向下遍历,所以需要设置cur,并且最后return cur就可以了。
def get(self, index: int) -> int:
cur
while index:
cur = cur.next
index -= 1
return cur
解决问题3:cur的初始化是dummyhead还是dummyhead.next?用一个最极端的例子,index=0时,while程序块不执行,直接输出cur,此时的cur为index=0的结点,所以cur初始化为dummyhead.next。
def get(self, index: int) -> int:
cur = dummyhead.next
while n:
cur = cur.next
n -= 1
return cur.val
解决问题4:没有考虑下标无效,return -1的情况。在这里发现需要设置能表示链表长度的变量,所以在__init__方法中加入链表长度变量,初始化为0。
def __init__(self):
self.dummyhead = ListNode()
self.dummyhead.next = head
self.length = 0
def get(self, index: int) -> int:
if index < 0 or index > self.length:
return -1
cur = dummyhead.next
while n:
cur = cur.next
n -= 1
return cur.val
考虑边界问题:index == self.length的时候return什么呢?如果长度时5,那么index=5的时候已经在链表之外了,所以这种情况也应该return-1。
def get(self, index: int) -> int:
if index < 0 or index >= self.length:
return -1
cur = dummyhead.next
while n:
cur = cur.next
n -= 1
return cur.val
头插法
最简单的结点插入。这个self啊啊啊啊啊记得写啊啊啊啊
def addAtHead(self, val: int) -> None:
newNode = ListNode(val)
newNode.next = self.dummyhead.next
self.dummyhead.next = newNode
看起来好像写完了,但是链表长度没更新。。。
def addAtHead(self, val: int) -> None:
newNode = ListNode(val)
newNode.next = self.dummyhead.next
self.dummyhead.next = newNode
length += 1
尾插法
找到最后一个结点(cur.next = None)最后一个结点指向新结点。更新链表长度啊啊啊啊!最后是None所以不用return。
def addAtTail(self, val: int) -> None:
newNode = ListNode(val)
cur = self.dummyhead
while cur.next != None:
cur = cur.next
cur.next = newNode
self.length += 1
在第index个结点之前插入结点
解决问题1:首先要找到第index个结点,在这个结点之前进行插入操作,这个插入操作和上面的头插法差不多,所以可以用while循环。
def addAtIndex(self, index: int, val: int) -> None:
newNode = ListNode(val)
while index:
index -= 1
解决问题2:需要指针不停的向后遍历,所以设置指针cur,但是cur初始化为什么?在第index个结点之前进行插入,首先要找到第index-1个结点,在这两个结点之间插入新结点,所以一定要保证第cur是遍历到第index-1位结点就进行插入操作。如果cur遍历到第index位,由于是单链表没有pre指针,所以无法找到第index-1位结点。所以在极端情况下index=0的时候while语句块不执行,此时就是在第0位结点前进行插入,所以cur赋值为dummyhead。然后进行插入操作,链表长度加一。
def addAtIndex(self, index: int, val: int) -> None:
newNode = ListNode(val)
cur = self.dummyhead
while index:
cur = cur.next
index -= 1
newNode.next = cur.next
cur.next = newNode
self.length += 1
删除第index个结点
思路同上很相似,就不写了。
错误第一版:没考虑边界问题啊啊啊啊
def deleteAtIndex(self, index: int) -> None:
cur = self.dummyhead
while index:
cur = cur.next
index -= 1
cur.next = cur.next.next
错误第二版:
链表长度为什么不更新啊啊啊啊啊!!!
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.length:
return
cur = self.dummyhead
while index:
cur = cur.next
index -= 1
cur.next = cur.next.next
正确版:
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.length:
return
cur = self.dummyhead
while index:
cur = cur.next
index -= 1
cur.next = cur.next.next
self.length -= 1
需要注意的地方
链表结构定义是:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
但是在整体代码中会出现以下几种代码(这两种是对的):
self.dummyhead = ListNode()
newNode = ListNode(val)#这个在代码里面定义了val的值。
这种是错的(这个给一个例子解释的是没定义val究竟是多少):
self.dummyhead = ListNode(val)
因为在建立链表结构的时候已经给了默认值val和next,所以下面使用的时候ListNode()的括号里如果不写东西就证明使用默认值,如果写了就一定要定义好使用的值。
整体代码但不正确
class ListNode:
def __init__(self,val = 0,next = None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummyhead = ListNode()
self.length = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.length:
return -1
cur = self.dummyhead.next
while index and cur:
cur = cur.next
index -= 1
return cur.val
def addAtHead(self, val: int) -> None:
newNode = ListNode(val)
newNode.next = self.dummyhead.next
self.dummyhead.next = newNode
self.length += 1
def addAtTail(self, val: int) -> None:
newNode = ListNode(val)
cur = self.dummyhead
while cur.next != None:
cur = cur.next
cur.next = newNode
self.length += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index >= self.length:
return
newNode = ListNode(val)
cur = self.dummyhead
while index:
cur = cur.next
index -= 1
newNode.next = cur.next
cur.next = newNode
self.length += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.length:
return
cur = self.dummyhead
while index:
cur = cur.next
index -= 1
cur.next = cur.next.next
self.length -= 1
错误的位置在def addAtIndex(self, index: int, val: int) -> None:这个方法里。
错误原因:
index=length的情况考虑错误,这个位置的插入就是尾插法,在最后的结点在插入一个结点。
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.length:
return
newNode = ListNode(val)
cur = self.dummyhead
if index == self.length:
self.addAtTail(val)
while index:
cur = cur.next
index -= 1
newNode.next = cur.next
cur.next = newNode
self.length += 1
报错:解答错误,因为在调用self.addAtTail方法时没有返回return,此时代码会继续运行,在下面通过while语句会再插入一个结点,所以就变成插入两个结点。
正确代码
class ListNode:
def __init__(self,val = 0,next = None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummyhead = ListNode()
self.length = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.length:
return -1
cur = self.dummyhead.next
while index and cur:
cur = cur.next
index -= 1
return cur.val
def addAtHead(self, val: int) -> None:
newNode = ListNode(val)
newNode.next = self.dummyhead.next
self.dummyhead.next = newNode
self.length += 1
def addAtTail(self, val: int) -> None:
newNode = ListNode(val)
cur = self.dummyhead
while cur.next != None:
cur = cur.next
cur.next = newNode
self.length += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.length:
return
newNode = ListNode(val)
cur = self.dummyhead
if index == self.length:
self.addAtTail(val)
return
while index:
cur = cur.next
index -= 1
newNode.next = cur.next
cur.next = newNode
self.length += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.length:
return
cur = self.dummyhead
while index:
cur = cur.next
index -= 1
cur.next = cur.next.next
self.length -= 1
# 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)
206.反转链表
第一想法
改变指针方向,具体怎么改不知道。。。
第二想法
解决问题1:想要改变指针的方向,首先需要遍历整个链表把每一个结点的指针都改变方向,所以设置cur,并且初始化cur=head;但是存在一个问题头结点指向哪里,所以设置一个新指针pre,因为头结点翻转之后的next=None,所以初始化pre=None。
解决问题2:循环终止条件是什么?最开始想要用链表长度作为终止条件,但是链表长度需要计算,所以直接用cur进行遍历,当cur指针遍历到None的时候循环终止,这代表cur已经到最后一个结点的后面了,在这个位置已经没有指针方向需要改变了,此时停止遍历。
解决问题3:改变cur指向的结点的指针方向时,原始指针的指向就丢失了,所以应该设置一个指针temp暂存原始指针的指向。
解决问题4:最后的return的头结点应该是什么?cur和pre一直在链表中遍历,但是循环的终止条件是cur=None,所以说此时pre在反转链表的第一个结点也就是头结点上,所以返回值应该是pre。
第一版错误代码:
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur = head
pre = None
while cur != None:
temp = cur.next
cur.next = pre
pre.next = cur
cur.next = temp
return pre
报错问题:
pre和cur都要向后移动,先pre后cur,因为一旦cur先移走了,pre没有参照无法移动。所以pre移动到现在cur的位置,cur保存的是temp中暂存的指针。
正确代码:
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur = head
pre = None
while cur != None:
temp = cur.next
cur.next = pre
pre = cur
cur = temp
return pre
还有一个递归方法,但是递归不太了解,学一学把这个方法补上。
总结
1、总想着动头节点head,设置变量之后还想动head,不好不好。一旦使用头结点进行遍历,那么头结点就会不停的改变,最后输出的时候就无法用头结点head来表示整个链表了。
2、返回链表的头结点一定是一个没变过的头结点。
3、写类这个东西啊,真的是每次写都不对,啊~
4、对于有一些边界的问题还是不清楚,很模糊,就比如最后一位包括还是不包括,最开始的究竟定义的是0还是1.处理边界条件今天的主要是用链表第一个结点进行测试。
5、指针变化的先后顺序需要严谨。
6、第二题哪个在第index个结点前插入结点,当index=length这个情况调用了尾插法,再想想有没有其他的方法,这个办法有点emmm。