算法练习之 19.删除链表倒数第n个节点算法

问题:给定一个链表,删除链表的倒数第 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来进行删除操作。因为是链表,所以我们不能够通过索引来获取链表的节点。只能通过链表头,进行遍历,直到找到需要删除的节点的位置。但是我们初始不知道链表的长度,所以就无法得到要删除的节点从头开始的位置。

  1. 所以我们可以先获取链表的长度,就可以知道了要删除节点的位置,再从头遍历,找到节点,删除。但是这种方法我们需要遍历两编链表,算法的时间复杂度与链表的长度和删除节点n有关,链表越长,n的位置越小,算法效率越低。
    算法:
  1. 因为链表的长度我们是必须要知道的,也就是必须要遍历一遍链表,所以有没有一种可以一遍就可以删除节点的方法呢?
    通过第一种方法,我们发现,需要遍历两次的原因就是我们无法通过遍历一遍时记住节点的位置。所以,可以加一个记录数组,用来记录遍历过得节点的指针值。当一遍遍历完时,我们就可以通过链表的长度已经删除节点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
}
  1. 上面两种方法的弊端我们都看的清清楚楚,但是为了要一次遍历后删除节点,就需要从题目入手了。题目需要我们删除的是倒数第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 
}

猜你喜欢

转载自blog.csdn.net/ywloveb/article/details/87377319