题目详情
本题是删除链表的倒数第n个数,因为链表的性质,所以必须得遍历才知道链表有多长。
为了知道每个节点是倒数第几个节点,我们这里用回溯来计数,从而统计自己是第几个数。
另外我们删除第n个数的逻辑是,用第n+1个数去指向n-1个数(倒数)。
不过这存在一个特殊情况。n-1的数必然存在,它可以为空,但n+1的数可能不存在,当我们要删除的数就在链表的头部时,n+1这个元素为空,我们可以用一个节点指向空,但绝不能用空节点指向另外一个节点。
所以我们需要去判断第n个元素是否是头节点,如果是,那么我们就返回头结点的next即可。
代码的效果是100%和99.19%
递归解法
这是我一个月前做的了,用的回溯思想,找到n-1和n+1的那个点。
其中也就一个特殊情况。
class Solution {
private boolean flag = false;// 用以强制退出递归
ListNode nSubOne = null,nPlusOne = null,nn=null;
public ListNode removeNthFromEnd(ListNode head, int n) {
backtrack(head,n);
//只需要考虑一个特殊情况:删除的是头元素,则无法用n+1->n-1来删除n,那么直接返回head的next即可
if(nn==head)
return head.next;
nPlusOne.next = nSubOne;
return head;
}
public int backtrack(ListNode p, int n){
if(flag)
return 0;
//判断是否为空
if(p==null)
return 0;
int count = backtrack(p.next,n);
count++;
//进行判断,找到倒数n-1,n和n+1个数
if(count==n-1)
nSubOne = p;
else if(count==n+1){
nPlusOne = p;
flag = true;
}
else if(count==n)
nn = p;
return count;
}
}
多指针解法
一个多月后再做此题,回溯忘的一干二净,第一反应是多指针。
总共三个指针,一个preN,一个nextN,一个tail。
tail领先preN一共n次next(也就是中间隔了n-1个点),而nextN领先了preN两次next(也就是一个点)。
当三个指针同时往后跑,tail.next为空时便说明走到链表尾了。而此时preN刚好走到倒数第n个点的前面,nextN刚好走到倒数第n个点的后面。此时preN.net = nextN即可。
这里和上面一样,有特殊情况,当n=链表长度时,也就是tail先跑了之后next为空,则直接返回head.next即可。
看代码:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//没有什么记忆了,感觉还是得多刷题,不然这种做过的题都没什么记忆,用什么方法还得想半天,其实很不应该
//需要考虑的是,这个是个链表,你只能一直访问下去
//需要找到第倒数第n个节点,
//只用一趟就解决
//因为n有效,所以我直接把第一个节点往后走n个,领先n,然后两个一起往后走
//这样的话,当领先的为null时,那么就能找到第n个了
//这里因为是要删除,所以,还得搞一个近的
//所以是n-1是n+1,总共三个指针
if(n==0){
return head;
}
ListNode ans=head, preN=head, nextN=head, tail=head;
//当链表长度等于n时,需要考虑,其他都不存在问题
for(int i=0; i<n; i++){
tail = tail.next;
}
if(tail==null){//n=链表长度
return head.next;
}
nextN = nextN.next.next;
while(tail.next!=null){
tail=tail.next;
preN = preN.next;
nextN = nextN.next;
}
preN.next = nextN;
return head;
}
}
时间复杂度都是一样的,都是O(n),没啥区别,空间复杂度也是O(1),比上面的常数项略多。
只是一个不同的思路。