【题目】
分别实现两个函数,一个可以删除单链表中倒数第K个节点,另一个可以删除双链表中倒数第K个节点。
【要求】
如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。
【解答-单链表】
如果链表为空或者K值小于1,这种情况下,参数是无效的,直接返回即可。除此之外,让链表从头开始走到尾,每移动一步,就让K的值减1。
链表:1 -> 2 -> 3,K=4,链表根本不存在倒数第4个节点。
走到的节点:1 -> 2 -> 3
K的变化:3 2 1
链表:1 -> 2 -> 3,K=3,链表倒数第3个节点是1节点
走到的节点:1 -> 2 -> 3
K的变化:2 1 0
链表:1 -> 2 -> 3,K=2,链表倒数第2个节点是2节点
走到的节点:1 -> 2 -> 3
K的变化:1 0 -1
由以上三种情况可知,让链表从头开始走到尾,每移动一步,就让K值减1,当链表走到结尾时,如果K值大于0,说明不用调整链表,因为链表根本没有倒数第K个节点,此时将原链表直接返回即可;如果K值等于0,说明链表倒数第K个节点就是第一个节点(head),此时直接返回head->next,也就是原链表的第二个节点,让第二个节点作为链表的头返回即可,相当于删除第一个节点;接下来,说明以下如果K值小于0,该如何处理。
先明确一点,如果要删除链表的头节点之后的某个节点,实际上需要找到要删除节点的前一个节点,比如:1 -> 2 -> 3,如果要删除节点2,则需要找到节点1,然后把节点1连到节点3上(1 -> 3),以此来达到删除节点2的目的。
如果K值小于0,如何找到要删除节点的前一个节点呢?方法如下:
(1)重新从头节点开始走,每移动一步,就让K的值加1。
(2)当K等于0时,移动停止,移动到的节点就是要删除节点的前一个节点。
这样做是非常好理解的,因为如果链表长度为N,要删除倒数第K个节点,很明显,倒数第K个节点的前一个节点就是第N-K个节点。在第一次遍历后,K的值变为K-N。第二次遍历时,K的值不断加1,加到0就停止遍历,第二次遍历当然会停到第N-K个节点的位置。
【代码实现】
struct Node{
int value;
Node *next;
Node(int data){
value = data;
}
};
static Node *removeLastKthNode(Node *head, int lastKth){
if (head == nullptr || lastKth < 1){
return head;
}
Node *cur = head;
while (cur != nullptr){
lastKth--;
cur = cur -> next;
}
if (lastKth == 0){
head = head -> next;
}
if (lastKth < 0){
cur = head;
while (++lastKth != 0){
cur = cur->next;
}
cur->next = cur->next->next;
}
return head;
}
对于双链表的调整,几乎和单链表的处理方式一样,注意last指针的重连即可。
【代码实现】
struct DoubleNode
{
int value;
DoubleNode *last;
DoubleNode *next;
DoubleNode(int data){
value = data;
}
};
static DoubleNode *removeLastKthNode(DoubleNode *head, int lastKth){
if (head == nullptr || lastKth < 1){
return head;
}
DoubleNode *cur = head;
while (cur != nullptr){
lastKth--;
cur = cur->next;
}
if (lastKth == 0){
head = head->next;
head->last = nullptr;
}
if (lastKth < 0){
cur = head;
while (++lastKth != 0){
cur = cur->next;
}
DoubleNode *newNext = cur->next->next;
cur->next = newNext;
if (newNext != nullptr){
newNext->last = cur;
}
}
return head;
}
【来源】
《程序员代码面试指南(IT名企算法与数据结构题目最优解)》左程云