给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
解法一
数组 自己看答案之前比较笨的做法,就是转为数组,删除了过后,再转为链表,但还是用来两遍循环。
def removeNthFromEnd1(head, n):
arr = []
while not head is None:
arr.append(head.val)
head = head.next
del arr[len(arr) - n]
if len(arr) == 0: return ListNode()
for i, value in enumerate(arr[::-1]):
if i == 0:
ans = ListNode(value)
else:
ans = ListNode(value, ans)
return ans
解法二
计算链表长度 参考力扣官方答案,先计算链表长度,然后就能确定删除倒数第n个节点,就是删除顺数第length - n + 1个节点。然后让删除的节点上一个节点指向下下一个节点就好了。
另外注意的是:在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了。
def removeNthFromEnd2(head, n):
"""计算链表长度"""
def getLength(head):
"""获取链表长度"""
length = 0
while head:
length += 1
head = head.next
return length
# 添加哑节点,使它的next指向链表的头节点
dummy = ListNode(0, head)
length = getLength(head)
cur = dummy # 当前节点指向哑节点
# 第length - n + 1个节点,就是我们需要删除的节点
for i in range(1, length - n + 1):
cur = cur.next
cur.next = cur.next.next
return dummy.next
解法三
栈 参考力扣官方答案,我们在遍历链表的同时将所有节点依次入栈。根据栈「先进后出」的原则,我们弹出栈的第 n 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。这样一来,删除操作就变得十分方便了。
def removeNthFromEnd3(head, n):
"""栈"""
dummy = ListNode(0, head)
stack = list()
cur = dummy
while cur:
stack.append(cur)
cur = cur.next
for i in range(n):
stack.pop()
prev = stack[-1]
prev.next = prev.next.next
return dummy.next
解法四
双指针(快慢指针) 由于我们需要找到倒数第 n 个节点,因此我们可以使用两个指针 first 和 second 同时对链表进行遍历,并且 first 比 second 超前 n 个节点。当 first 遍历到链表的末尾时,second 就恰好处于倒数第 n 个节点。
如果我们能够得到的是倒数第 n 个节点的前驱节点而不是倒数第 n 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将 second 指向哑节点,其余的操作步骤不变。这样一来,当 first 遍历到链表的末尾时,second 的下一个节点就是我们需要删除的节点。
这是我觉得最好的算法,因为扫描的长度最少。
def removeNthFromEnd4(head, n):
"""双指针,快慢指针"""
dummy = ListNode(0, head)
first = head
second = dummy
for i in range(n): # 让first先领先n个节点
first = first.next
# first和second同时前进
# 上面for和下面while遍历的长度和,加起来就是链表的长度L
while first:
first = first.next
second = second.next
second.next = second.next.next
return dummy.next
复杂度分析
- 时间复杂度:,其中 L 是链表的长度。
- 空间复杂度:,只是使用了常数个变量。