19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
此题目为链表题,拿出我们的杀手锏,链表解题经典三把斧:
- 哑巴节点
- 栈
- 快慢指针
关于内存问题:由于Swift及OC均有ARC内存机制,因此删除的节点内容未主动释放,如在手动内存管理的情况下,需要释放被删除节点的内存占用。
方法一、计算链表长度
先求出链表长度L,再将链表从头移动到L-n+1的位置,删除其下一个节点。
时间复杂度:O(n),一次求长度n,极端情况下的一次遍历,2n->O(n)
空间复杂度:O(1)
Swift
//计算链表长度, 删除l-n+1的位置
func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? {
let dummyNode = ListNode(0, head);
let len = getLenOfListNode(head)
var current:ListNode? = dummyNode
for _ in 1..<len-n+1 {
current = current?.next
}
current?.next = current?.next?.next
//MRC Or ARC?被释放的节点内存需要处理吗?
let ans = dummyNode.next
return ans
}
func getLenOfListNode(_ listNode:ListNode?) -> Int {
var len = 0
var current = listNode
while let cur = current {
current = cur.next
len += 1
}
return len
}
OC
//计算链表长度, 删除l-n+1的位置
- (ListNodeOC *)removeNthFromEnd:(ListNodeOC *)head atReverseIdx:(NSInteger)n {
//构造虚拟头节点
ListNodeOC *dummyNode = [[ListNodeOC alloc] initWithVal:0 next:head];
NSInteger len = [self lenOfListNode:head];
ListNodeOC *cur = dummyNode;
for (NSInteger i=1; i<len-n+1; i++) {
cur = cur.next;
}
cur.next = cur.next.next;
return dummyNode.next;
}
解法二、栈
先将链表入栈,再出栈n个元素后,栈顶部的元素就是我们需要删除的节点的前一个节点。
时间复杂度:O(n)
空间复杂度:O(n)
Swift
//入栈后弹栈n次即可
func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? {
//构造影子节点
let fickNode = ListNode(0)
fickNode.next = head
let stack = Stack<ListNode>()
var cur:ListNode? = fickNode
while let currentNode = cur {
stack.push(currentNode)
cur = currentNode.next
}
for _ in 0..<n {
let _ = stack.pop()
}
if let preNode = stack.top() {
preNode.next = preNode.next?.next
}
return fickNode.next
}
OC
- (ListNodeOC *)removeNthFromEnd:(ListNodeOC *)head atReverseIdx:(NSInteger)n {
ListNodeOC *dummyNode = [[ListNodeOC alloc] initWithVal:0 next:head];
StackOC *stack = [[StackOC alloc] init];
//先入栈
ListNodeOC *cur = dummyNode;
while (cur) {
[stack push:cur];
cur = cur.next;
}
//出栈n个元素
for (NSInteger i=0; i<n; i++) {
[stack pop];
}
//删除最后一个元素
cur = [stack top];
cur.next = cur.next.next;
[stack cleanAll];
return dummyNode.next;
}
双指针
创建哑巴节点,有两个指针均指向哑巴节点,首先移动第2个指针n次,此时第1、2个指针相距n个节点;然后同时移动1、2两个节点,直至第2个指针指向最后一个元素,此时的第1个指针指向的节点就是倒数第n个元素的前一个元素。
时间复杂度:O(n)
空间复杂度:O(1)
Swift
func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? {
//构造影子节点,为指针操作预留空间
let dummyNode = ListNode(0, head)
var first:ListNode? = dummyNode
var second:ListNode? = dummyNode
var current:ListNode? = dummyNode
for _ in 0..<n {
if let cur = current {
second = cur.next
current = second
}
}
//同时移动两个指针,直至2到达结尾
while let _ = current?.next {
first = first?.next
second = second?.next
current = second
}
first?.next = first?.next?.next
return dummyNode.next
}
OC
- (ListNodeOC *)removeNthFromEnd:(ListNodeOC *)head atReverseIdx:(NSInteger)n {
ListNodeOC *dummyNode = [[ListNodeOC alloc] initWithVal:0 next:head];
ListNodeOC *firstNode = dummyNode;
ListNodeOC *secondNode = dummyNode;
//先移动指针,让两个指针差值为n
for (NSInteger i=0; i<n; i++) {
secondNode = secondNode.next;
}
//同时移动两个指针,当第二个指针指向最后一个元素的时候,第一个指针指向的正是倒数第n个元素
while (secondNode.next) {
firstNode = firstNode.next;
secondNode = secondNode.next;
}
firstNode.next = firstNode.next.next;
return dummyNode.next;
}