写代码的第四天
24. 两两交换链表中的节点
第一想法
像前一天那道题一样,改变偶数位结点的指针就可以,但是怎么能知道这个结点是第偶数位呢?写不出来;还有一个问题是没考虑到的,不只是对偶数位指针指向前一位奇数位的修改,奇数位指针也是要修改的。(啊啊啊啊我的脑子去哪了~)。
肯定要有遍历所以肯定要设置变量cur但是初始值是啥感觉都行啊,啧想不出来设置不同的初始值区别在哪。有想到需要while循环,终止条件应该分两种情况,如果是奇数个结点就是cur.next.next == None,如果是偶数个结点,那就是cur.next等于None时停止。(卧槽,这块我居然想对了!)
思路
解决问题1:不论是奇数位结点还是偶数位结点都需要对结点指针进行改变,所以要设置指针变量cur遍历链表中的结点。对于cur来说初始化为什么?需要注意的是初始化变量是为了对他所遍历到的结点进行操作,在这道题中需要对相邻的两个结点进行操作,通俗地说就是这两个结点的位置发生了调换(这句话不太严谨,但我觉得这样说比较好理解),这就导致这两个结点之前的结点,它的指针指向发生了变化,本题我采用的还是单链表,无法找到当前结点的pre,所以如果要进行相邻两个结点的操作,那么cur就必须遍历到这两个结点之前的那个结点的位置。所以cur赋值为dummyhead。
解决问题2:交换节点的操作是什么?只看一个操作快:原始情况是dum->1->2->3,修改之后是dum->2,2->1,1->3
cur.next = cur.next.next
cur.next.next = cur.next
cur.next = cur.next.next.next
显而易见按照上面的dum->2,2->1,1->3这个步骤写下来,完全错误,因为没有考虑到在修改指针指向的时候,后续的链表有没有丢失这一问题。
解决问题3:dum->1:如果按照上述的cur.next = cur.next.next方式写,那么0->1这里的指针就丢失了,也就是找不到1这个结点的位置了,所以设置一个temp变量在执行这句话之前存储1结点的位置,temp = cur.next
temp = cur.next
cur.next = cur.next.next
cur.next.next = cur.next
cur.next = cur.next.next.next
解决问题4:2->1:这个时候1结点的位置已经暂存了,2的位置就是cur.next,此时执行cur.next.next = cur.next就应该变成cur.next.next = temp因为temp存储的才是结点1的位置。
temp = cur.next
cur.next = cur.next.next
cur.next.next = temp
cur.next = cur.next.next.next
解决问题5:1->3:现在1的位置有了存在temp中,但是3的位置在哪?此时会发现原本2->3,但是在上一步2->1了,指针发生变化,3的位置已经丢失,所以需要在最开始的时候在设置一个变量存储3的位置temp1=cur.next.next.next,这个时候再1->3,也就是temp.next = temp1
temp = cur.next
temp1 = cur.next.next.next
cur.next = cur.next.next
cur.next.next = temp
temp.next = temp1
解决问题6:上述几步只是完成了一次相邻结点的交换,cur需要向后遍历,遍历步骤是什么,是一个结点一个结点向后遍历吗?按照问题1中我们解释的cur要遍历到相邻需要修改的结点的前一个结点,所以cur=cur.next.next(通俗一点想:第一步要修改1和2,此时cur在0位,接下来要修改3和4,那么cur在2位,所以cur应该遍历的是从0直接到2)
temp = cur.next
temp1 = cur.next.next.next
cur.next = cur.next.next
cur.next.next = temp
temp.next = temp1
cur = cur.next.next
整体代码
错误第一版:head已经被修改了,现在的头结点已经变了,没变的只是dummyhead.next永远只想的都是头结点。
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummyhead = ListNode()
cur = dummyhead
while cur.next != None and cur.next.next != None:
temp = cur.next
temp1 = cur.next.next.next
cur.next = cur.next.next
cur.next.next = temp
temp.next = temp1
cur = cur.next.next
return head
报错:在这个测试用例中会发现上述代码根本没实现交换的作用!!!,在case2和case3中因为是空才输出正确。
错误第二版:我们只是设置了一个新的结点,但是这个结点dummyhead的next并没有指向head,也就是说基于这个结点所做的所有的事都是空的!!!!所以证明了为什么测试用例中只有空的例子能通过!!!
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummyhead = ListNode()
cur = dummyhead
while cur.next != None and cur.next.next != None:
temp = cur.next
temp1 = cur.next.next.next
cur.next = cur.next.next
cur.next.next = temp
temp.next = temp1
cur = cur.next.next
return dummyhead.next
正确版:
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummyhead = ListNode()
dummyhead.next = head
cur = dummyhead
while cur.next != None and cur.next.next != None:
temp = cur.next
temp1 = cur.next.next.next
cur.next = cur.next.next
cur.next.next = temp
temp.next = temp1
cur = cur.next.next
return dummyhead.next
19.删除链表的倒数第N个节点
(卧槽卧槽卧槽,这个题我没看解析,没看视频,我做出来了啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊)
整理一下思路哈哈哈哈
思路
题目是删除链表倒数第n个结点,想起了昨天做的那个链表操作里面的在第n个结点前的结点前插入结点,那道题要做的就是要找到第n-1个结点,在执行插入操作,所以在这道题中(删除操作是x.next = x.next.next)也要找到这个将要被删除的结点的前一个结点才能执行删除操作语句。
解决问题1:如何找到倒数第n个结点的前一个结点?因为我会的变量指针只是从头开始循环的,所以循环到哪个结点才是倒数第n个结点的前一个结点?这个时候我的想法是我需要知道链表的长度lens,一旦有了lens,用lens-n就表示的是在这个链表中的正着数的第几个结点的index(比如:长度为4,删除倒数第四个结点,其实删除的就是正着数第一个结点,第一个结点的index是0)。所以写了一个方法用来计算链表长度:
def lengthList(self,head):
dummyhead = ListNode(next = head)
cur = dummyhead
length = 0
while cur.next:
cur = cur.next
length += 1
return length
在这个方法中需要注意一点,它是在class里面的,所以第一个参数是self。
解决问题2:需不需要设置虚拟头结点?很有可能链表的第一个结点就是需要被删除的结点,所以此时head就变化了,这种情况下我认为设置一个虚拟头结点,并且用虚拟头结点.next来作为链表最后的输出更合理一点。
解决问题3:需要指针不停的向后遍历找到倒数第n个结点(也就是正着数的第index=lens-n个结点),所以设置cur指针,初始化为什么?我认为初始化的值就是看你想要用这个cur做什么,在这里面cur一个作用是遍历(cur=cur.next),第二个作用是进行删除操作(cur.next = cur.next.next),所以cur无非有两种赋值可能一个是cur=dummyhead,一种是cur=head。这个时候考虑一种情况如果现在要删除的结点就是head结点,那么如果cur初始化为head,他是没有办法执行(cur.next = cur.next.next)这样的删除操作的,因为他找不到head之前的结点,单链表没有pre功能。所以cur=dummyhead。
解决问题4:cur什么时候停止遍历,循环终止条件是什么?(要删除的倒数第n个结点,也就是正数第index=lens-n个结点)当循环到index-1的那一个结点的时候就应该停止了,如果循环到index结点才停止,那么无法执行删除操作,单链表没有pre功能。所以循环终止条件应该是while index:
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
lens = self.lengthList(head)
dummyhead = ListNode(next=head)
cur = dummyhead
index = lens - n
while index:
cur = cur.next
index -= 1
cur.next = cur.next.next
return dummyhead.next
看起来很合理是吧,但是不对,没有考虑特殊情况!!!!!!
解决问题5:题中给出n的范围是[1,链表长度lens]左闭右闭,所以现在开始考虑边界条件:
lens=0,n=1的时候:有区别的就是这里!!!也就是说这是个空链表,但是又要删除倒数第一个结点,结果就是一定会报错,所以加入一行判断语句即可if head == None: return
lens=0,n=lens的时候;根据题给的条件,不合理不用考虑了。
lens=1,n=1的时候:相当于只有一个结点,然后就删除这个结点,可通用上面的代码块(画个图代入一下就明白了)。
lens=1,n=lens的时候:同上。
lens=正常的正整数,n=1的时候:相当于删除链表最后一个结点,可通用上面的代码块(画个图就可以了)。
lens=正常的正整数,n=lens的时候:相当于删除链表头结点,可通用上述代码块。
正确代码:
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
if head == None:
return
lens = self.lengthList(head)
dummyhead = ListNode(next=head)
cur = dummyhead
index = lens - n
while index:
cur = cur.next
index -= 1
cur.next = cur.next.next
return dummyhead.next
def lengthList(self,head):
dummyhead = ListNode(next = head)
cur = dummyhead
length = 0
while cur.next:
cur = cur.next
length += 1
return length
双指针(我的脑子想不出来这种做法的)
思路:设置快慢指针都从dummyhead开始遍历(原因同上),但是快慢指针的区别在哪呢,快在哪,慢在哪?要删除倒数第n个结点,就需要找到倒数第n-1个结点,所以需要在快指针指向空的时候,慢指针指向倒数第n-1个结点,这样就可以用慢指针进行删除操作了。所以设置了快指针先走n步,然后慢指针再走。(画个图就明白了)
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummyhead = ListNode(next = head)
fast = slow = dummyhead
while n:
fast = fast.next
n -= 1
while fast.next:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummyhead.next
直接让快指针遍历到None,而不是指向空也可以,就是快指针比慢指针早走n+1步,画个图就明白了。
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummyhead = ListNode(next = head)
fast = slow = dummyhead
while n+1:
fast = fast.next
n -= 1
while fast:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummyhead.next
面试题 02.07. 链表相交
初始想法:比较结点中的数值,如果从某一个数值开始后面的数都相等那么这个结点就是相交的起始结点。大错特错
在这道题中,链表相交指的是指针相等!!!!
然后我的想法是给两个链表设置两个指针变量,都从后向前遍历,如果遍历相等的结点,那此时这个结点就是相交的结点。
解决问题1:单链表让指针从后向前移动,这个想法实在是太刺激了哈哈哈哈,单链表怎么可能从后向前找pre呢,那就换个想法,只能是从前向后遍历,所以现在要考虑相交的结点那就是说明从这个结点开始后面的结点都是一致的,那么我们选择链表中较短的那条链表的长度,将长链表的指针变量先遍历到一个结点,这个结点到链表最后一个结点的长度和短链表长度相等,从这个位置开始遍历,一旦遇到相等的就返回结果。
解决问题2:要求链表长度,所以写一个方法求链表长度
上面的代码是设置了头结点,但是这个发现链表长度不需要头结点,head也不会改变,所以也可以这样写。
def length(self,head):
cur = head
lens = 0
while cur:
cur = cur.next
lens += 1
return lens
解决问题3:链表A长的时候就先遍历A,让指针到和链表B长度相等的位置,如果lianbiaoB长就先遍历B,同样的。
if lensA > lensB:
lens = lensA-lensB
cur = headA
while lens:
cur = cur.next
lens -= 1
while lensB:
if cur == headB:
return headB
cur = cur.next
headB = headB.next
lensB -= 1
return None
if lensA <= lensB:
lens = lensB-lensA
cur = headB
while lens:
cur = cur.next
lens -= 1
while lensA:
if cur == headA:
return headA
cur = cur.next
headA = headA.next
lensA -= 1
return None
看起来很合理对不对,但是没有考虑边界条件所以有些测试用例是无法通过的,比如当链表A或链表B为空的时候,不需要进行下面的任何比较操作,因为不可能有交点。所以添加代码:
if lensA == 0 or lensB == 0:
return None
正确代码1:
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
lensA = self.length(headA)
lensB = self.length(headB)
if lensA == 0 or lensB == 0:
return None
if lensA > lensB:
lens = lensA-lensB
cur = headA
while lens:
cur = cur.next
lens -= 1
while lensB:
if cur == headB:
return headB
cur = cur.next
headB = headB.next
lensB -= 1
return None
if lensA <= lensB:
lens = lensB-lensA
cur = headB
while lens:
cur = cur.next
lens -= 1
while lensA:
if cur == headA:
return headA
cur = cur.next
headA = headA.next
lensA -= 1
return None
def length(self,head):
cur = head
lens = 0
while cur:
cur = cur.next
lens += 1
return lens
我们会发现在这个代码当中有一些是几乎重复的,就是在长度相等之后移动指针向后,所以可以进行代码复用。但是我还没想明白怎么写,等我学学把这补上。
142.环形链表II
首先要判断是否有环,然后再确定环开始的位置。最初的想法是如果指针变量没有指向空的就证明有环,但是这种判断方法会陷入死循环,不可取。
思路
用快慢指针(我的脑子想不出来)
解决问题1:快慢指针每次走几步?设置快指针走两步,慢指针走一步,这样的话就相当于快指针相对慢指针每次走一步,所以无论如何只要有环快慢指针就一定会相遇。不涉及到head头结点修改,所以快慢指针都初始化为head。
解决问题2:入口位置在哪?也就是说满足什么条件的时候才能说这个点就是入口点呢?这个问题的前提是确定存在环,能得出的结论就是fast和slow指针已经在环里面相遇了,依据这个条件写出了数学公式进行推倒,这个在卡哥的视频中有详细解释。那也就是说从头结点和环中相遇结点这两个位置同时进行结点遍历,这两个结点相遇的地方就是环的入口!!!所以设置了index1=head和index2=fast(slow也行,两个结点相遇的那个结点,用哪个表示都一样),让index1和index2不停的同时向下遍历,当index1==index2时,return index1或者index2都行,代表的位置一样。
解决问题3:一定要记得没环的情况!!!!!!
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast = head
slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
index1 = head
index2 = fast
while index1 != index2:
index1 = index1.next
index2 = index2.next
return index1
return None
总结
1、对于是否要设置虚拟结点,一点小小的看法就是如果最后可以用return head输出最后的链表,就不用设置虚拟头结点;如果head已经改变了那就要设置一个虚拟头结点,这样虚拟头结点.next一定代表修改后链表的头结点。(如有问题请指正,谢谢)
2、有些好的做法现在的脑子真是想不到一点,都是最笨的方法在做,啊啊啊啊啊。
3、边界条件刻在脑子里好吗!!!!
4、环的这个题太刺激了,哈哈哈哈代码的尽头是数学哈哈哈哈。