问题:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
定义:
链表定义如下:
//Definition for singly-linked list.
type ListNode struct {
Val int
Next *ListNode
}
分析
要删除倒数第n个节点,我们需要知道倒数第n+1个节点的值,这样才能通过Next来进行删除操作。因为是链表,所以我们不能够通过索引来获取链表的节点。只能通过链表头,进行遍历,直到找到需要删除的节点的位置。但是我们初始不知道链表的长度,所以就无法得到要删除的节点从头开始的位置。
- 所以我们可以先获取链表的长度,就可以知道了要删除节点的位置,再从头遍历,找到节点,删除。但是这种方法我们需要遍历两编链表,算法的时间复杂度与链表的长度和删除节点n有关,链表越长,n的位置越小,算法效率越低。
算法:
- 因为链表的长度我们是必须要知道的,也就是必须要遍历一遍链表,所以有没有一种可以一遍就可以删除节点的方法呢?
通过第一种方法,我们发现,需要遍历两次的原因就是我们无法通过遍历一遍时记住节点的位置。所以,可以加一个记录数组,用来记录遍历过得节点的指针值。当一遍遍历完时,我们就可以通过链表的长度已经删除节点n来获取删除节点在数组中的索引,通过索引可以获取到链表,进行删除。
但是这种方法的弊端就是额外增加了内存开销,因为需要使用一个和链表一样长度的数组,虽然在时间上降低了运行时间,但是对于大型的链表来说无疑极大的增加了内存开销。
func removeNthFromEnd(head *ListNode, n int) *ListNode {
l := head
i := 0
temp := make([]*ListNode, 0)
for l != nil {
temp = append(temp, l)
i++
l = l.Next
}
if n == i {
l := head.Next
head.Next = nil
return l
}
if n == 1 {
temp[i-2].Next = nil
return head
}
temp[i-n-1].Next = temp[i-n].Next
temp[i-n].Next = nil
return head
}
- 上面两种方法的弊端我们都看的清清楚楚,但是为了要一次遍历后删除节点,就需要从题目入手了。题目需要我们删除的是倒数第n个节点,我们可以逆向思维来一下,如果将链表尾部当做头,那么删除的就是正数第n个节点了。但是我们这个链表是单向的,所以无法反向进行遍历。此时从这里我们就看出了,正数第n个节点,我们只需要从前数n个,就可以了。但是对于倒数,我们可以设置一个索引l1、l2,其中l1和l2之间始终相差n+1。从头开始遍历时,当l2达到尾部时,那么我们的l1不就正好是倒数第n+1个节点了。
上图显示的时第三种方法的算法示意图,这里我们只需要保持l1和l2相差n+1,当l2为空的时候l1就达到了倒数第n+1个节点。
func removeNthFromEnd(head *ListNode, n int) *ListNode {
var l1 *ListNode //节点1
l2 := head //节点2
i1 := 0
i2 := 1
for l2 != nil { //移动节点2
if i2-i1 == n+1{ //这里我们找倒数n+1 个节点
if l1 == nil{
l1 = head
}else {
l1 = l1.Next
}
i1++
}
i2++
l2 = l2.Next
}
if i1 == 0 { //删除第一个的情况
t := head.Next
head.Next = nil
return t
}
l1.Next = l1.Next.Next
return
}