「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
题目:给定一个链表,删除此链表的倒数第N个节点,之后返回链表的头节点。
解题思路
由题可知,其最终的返回结果是删除节点之后的链表的头节点,如果按照常规思路,则需要判断当前删除节点是否为头节点,并且根据是和否做两种判断,代码逻辑会显得较为混乱,此处可以新增一个头节点,将给定的链表作为新创建的头结点的next,之后删除的逻辑就相对而言较为简单,删除之后返回头节点的next即可,代码如下:
public static ListNode removeNthFromEnd(ListNode head, int n) {
int len =0;
ListNode cur = head;
ListNode headpoint = new ListNode(0, head);
ListNode temp = headpoint;
while(cur!=null){
len++;
cur = cur.next;
}
for(int i=1;i<len-n+1;i++){
temp = temp.next;
}
temp.next = temp.next.next;
return headpoint.next;
}
此方式获得的是删除节点的前一个节点,之后根据简单的链表删除逻辑即可得到最终结果,其时间复杂度为,空间复杂度为。LeetCode的结果显示超过100%。但此题是扫描了两次链表得到的结果,题目的进阶要求为是否可以只扫描一次得到结果,如何实现?
进阶解法(只扫描一遍链表)
另一种思路是依然事先创建一个空的头节点,之后next指向head,依次遍历链表,将链表元素入栈(利用栈先进后出的特性,链表的倒数第n个节点即为第n个出栈的元素),之后将要删除的元素出栈得到栈顶元素即可,根据链表删除的特性可以很简单得到结果,代码如下:
public static ListNode removeNthFromEnd2(ListNode head, int n) {
ListNode headpoint = new ListNode(0, head);
ListNode cur = headpoint;
Stack<ListNode> s = new Stack<>();
while (cur!=null){
s.push(cur);
cur = cur.next;
}
for(int i=0;i<n;i++){
s.pop();
}
ListNode peak = s.peek();
peak.next = peak.next.next;
return headpoint.next;
}
此方法的时间复杂度为,空间复杂度为。
快慢指针
另一种更巧妙的方法是使用双指针来解,若要删除链表倒数第n个节点,只需要首先设置快慢指针,使得快慢指针的相距间隔为n即可,之后分别移动快慢指针,使得快指针为空,之后慢指针指向的就是待删除元素的前一个结点,根据链表删除规则即可完美删除,代码如下:
public static ListNode removeNthFromEnd3(ListNode head, int n) {
ListNode headpoint = new ListNode(0, head);
ListNode low = headpoint;
ListNode fast = head;
for(int i=0;i<n;i++){
fast = fast.next;
}
while (fast!=null){
fast = fast.next;
low = low.next;
}
low.next = low.next.next;
return headpoint.next;
}
上述方法的时间复杂度仍然是, 因为只使用了常数个变量,因此空间复杂度为。