「这是我参与11月更文挑战的第 9 天,活动详情查看:2021最后一次更文挑战」
刷算法题,从来不是为了记题,而是练习把实际的问题抽象成具体的数据结构或算法模型,然后利用对应的数据结构或算法模型来进行解题。个人觉得,带着这种思维刷题,不仅能解决面试问题,也能更多的学会在日常工作中思考,如何将实际的场景抽象成相应的算法模型,从而提高代码的质量和性能
删除链表的倒数第n个节点
题目描述
给定一个链表,删除链表的倒数第 n **个结点,并且返回链表的头结点
进阶:你能尝试使用一趟扫描实现吗?
示例
示例 1
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2
输入:head = [1], n = 1
输出:[]
示例 3
输入:head = [1,2], n = 1
输出:[1]
提示:
- 链表中结点的数目为
sz 1 <= sz <= 300 <= Node.val <= 1001 <= n <= sz
解题
解法一:常规法
思路
这种是最容易想到的,就是先遍历一遍链表,求出链表的长度len。那要删除倒数第n个节点,其实就是删除正数第len-n+1个节点,所以再次遍历链表的时候,找到第len-n+1个节点的前驱结点,就可以删除倒数第n个节点了
这种比较简单,这里不再放代码,主要是介绍通过下边两种方式实现
解法二:栈
思路
相信看到用栈来实现,你马上就知道怎么做了
- 遍历链表,并将每一个结点压入栈中
- 从栈中弹出的第n+1个节点,就是我们要删除的结点的前驱结点(pre)
- 找到了前驱结点就好删除了(pre.Next = pre.Next.Next)
代码
func removeNthFromEnd(head *LinkList.Node, n int) *LinkList.Node {
stack := []*LinkList.Node{}
dummy := &LinkList.Node{0, head}
for curr:=dummy; curr!=nil;curr=curr.Next {
stack = append(stack, curr)
}
pre := stack[len(stack) - n - 1] //获取前驱结点
pre.Next = pre.Next.Next
return dummy.Next
}
解法三:双指针
思路
删除倒数第n个节点,如果有一个指针先向前移动n个位置,然后另一个指针从头结点和上一个结点同时以步长为1向前移动,当前一个结点到达尾节点的时候,后边那个节点的位置,就是我们要删除的倒数第n个节点。因为要删除结点,需要知道该节点的前驱结点,所以,此时我们可以借助哨兵头结点来辅助时间,具体请看代码
代码
func removeNthFromEnd1(head *LinkList.Node, n int) *LinkList.Node {
dummy := &LinkList.Node{0, head} //这里加了一个哨兵结点的原因是为了方便找到待删除结点的前驱结点(链表的问题应该经常想到哨兵结点,或者叫虚拟头结点)
first, second := head, dummy
//first先走n个节点
for i:=0; i<n; i++ {
first = first.Next
}
for ;first != nil; first = first.Next {
second = second.Next //因为second在first的前一个位置开始遍历的,所以当first到尾部的时候,second就在倒数第n个节点的前一个位置
}
second.Next = second.Next.Next
return head
}