题目概述:
链接:点我做题
一、横冲直撞的暴力解法
这是我第一次做这题的时候想过的方法,因为看了拓展说你只能遍历一次这个链表,所以想着如果只能遍历一次,那么我在每个结点都确定一下它是否是倒数第n个结点就可以了,怎么确定呢?我们就要考虑倒数第n个结点的特性是什么了:
没错,倒数第n个结点的特点就是往前走n步必然遇到空指针。
并且考虑到删除单链表结点需要它的前继和头结点的删除问题,我们设置了一个dummyhead作为哨兵结点链接在head前面,设置一个prev每次都走在遍历用的指针cur的后面一个,代码如下:
代码:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
ListNode* cur = head;
ListNode* dummyhead = new ListNode(0, head);
ListNode* prev = dummyhead;
while (cur != nullptr)
{
ListNode* test = cur;
int flag = 0;
int number = n;
while (number--)
{
test = test->next;
if (test == nullptr)
{
flag = 1;
}
}
if (flag == 1)
{
break;
}
cur = cur->next;
prev = prev->next;
}
prev->next = cur->next;
delete cur;
ListNode* ret = dummyhead->next;
delete dummyhead;
return ret;
}
};
这个解法的缺点是很明显的,看起来值遍历了一遍链表,其实还是遍历了两遍(为了确定cur所指结点是否为倒数第n个结点又遍历了一遍链表),而且还是嵌套遍历,时间复杂度显然是
O
(
n
2
)
O(n^2)
O(n2)。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
二、用链表长度来确定倒数第N个结点
首先遍历一遍链表得到链表长度size,然后设置一个哨兵头结点dummyhead,然后从dummyhead从头开始走size - n步,就会走到待删除节点的前一个结点,然后进行删除操作即可。
代码:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
int len = getListlen(head);
ListNode* dummyhead = new ListNode(0, head);
ListNode* cur = dummyhead;
for (int i = 1; i <= len - n; i++)
{
cur = cur->next;
}
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
ListNode* ret = dummyhead->next;
delete dummyhead;
return ret;
}
int getListlen(ListNode* head)
{
int ret = 0;
while (head)
{
ret++;
head = head->next;
}
return ret;
}
};
这个方法的实质还是遍历了两遍链表,不过两次遍历不是嵌套的,所以时间复杂度优化到了
O
(
n
)
O(n)
O(n)。
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
三、利用栈来删除倒数第N个结点
栈是一种先进后出的数据结构,STL中也有栈的模板,所以我们显然可以用栈来解决这个问题。考虑到删除头结点的问题,我们特意设置一个dummyhead作为哨兵头结点,然后先通过一遍遍历把所有的节点都压入栈中,然后
p
o
p
(
)
pop()
pop()n次,此时的栈顶节点就是待删除的节点的前一个结点,执行删除操作,返回
d
u
m
m
y
h
e
a
d
−
>
n
e
x
t
dummyhead->next
dummyhead−>next,本题就结束了。
代码:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
//栈的解法
stack<ListNode*> st;
ListNode* dummyhead = new ListNode(0, head);
ListNode* cur = dummyhead;
while (cur)
{
st.push(cur);
cur = cur->next;
}
for (int i = 1; i <= n; i++)
{
st.pop();
}
ListNode* prev = st.top();
cur = prev->next;
prev->next = cur->next;
delete cur;
ListNode* ret = dummyhead->next;
delete dummyhead;
return ret;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
四、前后指针法
仿照删除数组的倒数第N个元素的前后指针法,链表也可以使用这个方法,同样,为了处理头结点删除的情况,设置一个dummyhead作为哨兵头结点,设置两个指针fast和slow从dummyhead开始走,fast先走n + 1步,然后slow和fast一起开始走,当fast走到nullptr的时候,slow就走到了待删除节点的前一个结点,执行删除操作即可,然后返回dummyhead->next。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
//双指针法
//两个指针 一个先走n步 然后两个同时走
//当一个指针走到尾巴时,另一个指针相当于走了size - n步
//就到达了我们想要让它到达的地方
//加了一个dummyhead相当于要先走n + 1步
ListNode* dummyhead = new ListNode(0, head);
ListNode* fast = dummyhead;
ListNode* slow = dummyhead;
for (int i = 0; i <= n; i++)
{
fast = fast->next;
}
while (fast)
{
fast = fast->next;
slow = slow->next;
}
ListNode* tmp = slow->next;
slow->next = tmp->next;
delete tmp;
tmp = dummyhead->next;
delete dummyhead;
return tmp;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)