点 击 上 方 蓝 字 , 和 我 一 起 学 技 术 。
链接
https://leetcode.com/problems/remove-nth-node-from-end-of-list 难度
描述
Given a linked list, remove the n -th node from the end of list and return
its head. 给定一个链表,要求移除导数第n个元素,并且返回新链表的head Given linked list: 1- >2->3->4->5, and _n_ = 2 . After removing the second node from the end, the linked list becomes 1->2->3->5 .
Given n will always be valid. Could you do this in one pass? 题解
题目的意思非常直白,不需要太多思考就能理解。 但是上手去做的话会有一点小问题,因为如果是数组很好办,我们直接可以求到数组的长度,导数第N个元素也非常容易确定。 但是如果是链表的话就不同了,因为我们并不知道链表的长度,所以没办法直接获取需要删除节点的位置 。 既然直接没有办法求到,那么可以间接去求嘛,所以很自然地可以想到两次遍历的方法。 两次遍历
这个方法非常直观,基本上可以说是顾名思义。 我们对这个链表遍历两次,第一次求到链表的长度 ,这样我们就可以推算到倒数第N个数是正数第几个数了。 第二次我们移动对应的长度 ,找到需要删除的节点,将它移除即可。 虽然算法不难,但是这题当中藏着trick,隐藏了出题人深深的恶意。 不相信的话,可以试着不看答案写写看,基本上一定会错上一两次。 原因很简单,因为会有一些特殊情况需要考虑。 比如,特殊情况1: 链表当中只有一个元素,显然这个时候根本不需要移动,也不用删除,直接return None就好了。 但是如果我们使用常规方法的话,是无法删掉的,必须要特殊判断这种情况。 特殊情况2: 这个要删的元素刚好是第一个head元素 ,这种情况也没有办法常规解决,也需要特殊判断。 # Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution :def removeNthFromEnd (self, head: ListNode, n: int) -> ListNode: l = pnt = head# 计算链表长度 while pnt: l += 1 pnt = pnt.next# 如果长度为1,直接return None if l == 1 :return None # 计算删除需要移动的长度 l = l - n - 1 # 如果小于0,说明需要删除第一个元素,那么直接return head.next。 if l < :return head.next pnt = headfor i in range(l): pnt = pnt.next pnt.next = pnt.next.nextreturn head
一次遍历
上面的这种算法中规中矩,基本上没有难度。 那么有没有更好的算法,比如我们能不能做到只遍历链表一次呢? 这样不就优化了复杂度了吗? 当然是可以的,说来这种算法也非常巧妙。 如果说我们把链表想象成一条跑道,而把移动遍历的指针想象成跑道上的运动员。 我们现在不知道跑道有多长,但是我们想要知道距离终点30米的位置。 我们还知道运动员的奔跑速度都一样。 应该怎么做呢? 我们可以先让一个运动员先跑30米,然后再派另外一个运动员从起点出发。 这样,当第一个运动员跑到终点的时候,第二个运动员所在的位置就是距离终点30米的位置。 我们把上述的运动员换成指针,跑道换成链表,就是这题的解法了。 # Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution :def removeNthFromEnd (self, head: ListNode, n: int) -> ListNode: pnt1 = head# 第一个指针先跑n步 for i in range(n): pnt1 = pnt1.next# 注意,如果是删除head,会出现跑过头的情况,需要判断 if pnt1 is None :return head.next pnt2 = head# 移动第一个指针,直到结尾 while pnt1.next: pnt1 = pnt1.next pnt2 = pnt2.next# 删除pnt2.next位置的节点 pnt2.next = pnt2.next.nextreturn head
看起来第二种方法似乎更优一些,但实际上如果分析复杂度的话,两者都是 的复杂度,并没有高下之分。 而且比较奇葩的是,我用CPP提交的时候是能明显感觉出来第二种方法优于第一种几毫秒的,但是当我换成了Python之后,第二种方法的耗时反而还更长了。 可见,快慢都是相对的,一般情况下来说在复杂度相同的情况下,优化常数意义不大。 虽然搞了半天,并没有什么效果,但至少我们明白了这点,也算是收获。 好了,今天的文章就到这里,如果觉得有所收获,请顺手点个在看 或者转发吧,你们的支持是我最大的动力。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69966956/viewspace-3136205/
,如需转载,请注明出处,否则将追究法律责任。