剑指Offer - 面试题18-1:删除链表的节点

简介: 剑指Offer - 面试题18-1:删除链表的节点

题目

在O(1)时间内删除链表节点。

给定单项链表的头节点和一个节点指针,定义一个函数在O(1)时间内删除该节点。链表节点与函数的定义如下:

typedef struct ListNode 
{
  int m_nValue;
  struct ListNode* m_pNext;
}ListNode;
void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted);

分析

线性查找

要删除的位置有三种情况

1、最后节点要删除,直接删除然后前一个结点指向NULL

2、头部(第一个存数据的节点)删除,处理较为复杂,这里我们设置一个哨兵NewHead,让NewHead.next = head,然后从哨兵开始遍历,最后返回NewHead.next就可以完美解决!

3、删除的位置是中间部分,就正常p.next = p.next.next,删除节点。

情况1和情况三可以整合,然后再加个哨兵,就可以统一三种情况。实现如下


C

#include<stdio.h>
#include<stdlib.h>
typedef struct ListNode 
{
  int m_nValue;
  struct ListNode* m_pNext;
}ListNode;
ListNode* CreateEndLinkList()//尾插法创建单链表
{
  ListNode* head = NULL;
  ListNode* q = NULL;
  ListNode* p = NULL;
  head = (ListNode*)malloc(sizeof(ListNode));//申请头节点空间
  q = head;
  if (NULL == head)
  {
    perror("申请空间失败");
    exit(EXIT_FAILURE);
  }
  head->m_nValue = 0;
  head->m_pNext = NULL;
  int num = 10;
  int size = 5;
  while (size > 0)
  {
    p = (ListNode*)malloc(sizeof(ListNode));//申请子节点空间
    p->m_nValue = num++;
    p->m_pNext = NULL;//尾插入
    q->m_pNext = p;
    q = p;
    size--;
  }
  return head;
}
void PrintLinkList(ListNode* head)//输出链表数据
{
  ListNode* p = head->m_pNext;
  printf("head->");
  while (NULL != p)
  {
    printf("%d->", p->m_nValue);
    p = p->m_pNext;
  }
  printf("NULL\n");
}
ListNode* DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted)//删除链表的节点
{
  if (*pListHead == NULL || pToBeDeleted == NULL)
  {
    return *pListHead;
  }
  ListNode* NewHead = (ListNode*)malloc(sizeof(ListNode));
  if (NULL == NewHead)
  {
    perror("申请空间失败\n");
  }
  NewHead->m_pNext = *pListHead;//创建新的一个假头,防止头节点被删除
  ListNode* p = NewHead;
  //不能走到空 或者找到目标节点
  while (p->m_pNext != NULL && p->m_pNext != pToBeDeleted)
  {
    p = p->m_pNext;
  }
  //防止要删除的节点不在pListHead链表中
  if (p->m_pNext != NULL && p->m_pNext == pToBeDeleted)
  {
    ListNode* tmp = p->m_pNext;
    p->m_pNext = p->m_pNext->m_pNext;
    free(tmp);
    tmp = NULL;
  }
  return NewHead->m_pNext;
}
int main()
{
  ListNode* head = CreateEndLinkList();//10 11 12 13 14
  PrintLinkList(head);
  ListNode* p = head->m_pNext->m_pNext->m_pNext;//指向12
  //因为设计的链表头部不存储数据,所以从头下面一个开始
  PrintLinkList(DeleteNode(&head->m_pNext, p));//删除12
  return 0;
}

但是时间复杂度位O(n);不满足要求,因为链表只能从前到后遍历,所哟时间复杂度必然是O(n);所以跳出常规思维,能否从要删除的节点本身来解决。

披着羊皮的狼 - 替罪法


因为我们不知道要删除的节点的前一个节点,所以我们不能删除目标节点A,但是可以让B把所有信息给A,然后把B删除掉。细思极恐啊。

我们删除的不是A,而是A下面的节点B,现在的节点B实际是披着羊皮的狼。


这里有三种情况:

1、删除节点在最后,没有“替罪羊”。就只能用开始的线性遍历解决。

2、删除节点在开头,直接删除当前节点。

3、删除节点在中间,就用上述的替罪法


*时间复杂度为((n-1)O(1) + O(n))/n 等于O(1);空间复杂度O(1);满足要求


C

#include<stdio.h>
#include<stdlib.h>
typedef struct ListNode
{
  int m_nValue;
  struct ListNode* m_pNext;
}ListNode;
ListNode* CreateEndLinkList()//尾插法创建单链表
{
  ListNode* head = NULL;
  ListNode* q = NULL;
  ListNode* p = NULL;
  head = (ListNode*)malloc(sizeof(ListNode));//申请头节点空间
  q = head;
  if (NULL == head)
  {
    perror("申请空间失败");
    exit(EXIT_FAILURE);
  }
  head->m_nValue = 0;
  head->m_pNext = NULL;
  int num = 10;
  int size = 5;
  while (size > 0)
  {
    p = (ListNode*)malloc(sizeof(ListNode));//申请子节点空间
    if (NULL == p)
    {
      perror("申请空间失败!\n");
      exit(EXIT_FAILURE);
    }
    p->m_nValue = num++;
    p->m_pNext = NULL;//尾插入
    q->m_pNext = p;
    q = p;
    size--;
  }
  return head;
}
void PrintLinkList(ListNode* head)//输出链表数据
{
  ListNode* p = head;
  printf("head->");
  while (NULL != p)
  {
    printf("%d->", p->m_nValue);
    p = p->m_pNext;
  }
  printf("NULL\n");
}
ListNode* DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted)//删除链表的节点
{
  if (*pListHead == NULL || pToBeDeleted == NULL)
  {
    return *pListHead;
  }
  if (pToBeDeleted->m_pNext != NULL)//情况三
  {
    ListNode* tmp = pToBeDeleted->m_pNext;
    pToBeDeleted->m_nValue = tmp->m_nValue;
    pToBeDeleted->m_pNext = tmp->m_pNext;
    free(tmp);
    tmp = NULL;
    return *pListHead;
  }
  else if (*pListHead == pToBeDeleted)//情况一
  {
    free(pToBeDeleted);
    return (*pListHead)->m_pNext;
  }
  else//情况二
  {
    ListNode* p = *pListHead;
    //不能走到空 或者找到目标节点
    while (p->m_pNext != NULL && p->m_pNext != pToBeDeleted)
    {
      p = p->m_pNext;
    }
    //防止要删除的节点不在pListHead链表中
    if (p->m_pNext != NULL && p->m_pNext == pToBeDeleted)
    {
      ListNode* tmp = p->m_pNext;
      p->m_pNext = p->m_pNext->m_pNext;
      free(tmp);
      tmp = NULL;
    }
    return *pListHead;
  }
  return *pListHead;
}
int main()
{
  ListNode* head = CreateEndLinkList();//10 11 12 13 14
  PrintLinkList(head->m_pNext);
  ListNode* p = head->m_pNext->m_pNext->m_pNext;//指向12
  //因为设计的链表头部不存储数据,所以从头下面一个开始
  PrintLinkList(DeleteNode(&head->m_pNext, p));//删除12
  return 0;
}


测试删除头部

测试删除尾部

测试删除中间

本章完1

目录
相关文章
|
6月前
|
机器学习/深度学习 算法
24. 两两交换链表中的节点, 19.删除链表的倒数第N个节点 ,面试题 02.07. 链表相交
1. **两两交换链表中的节点**:通过引入虚拟头结点,使所有节点都能采用统一的交换逻辑,避免对头结点单独处理。 2. **删除链表的倒数第N个节点**:利用双指针技巧,让快慢指针保持N个节点的距离,当快指针到达末尾时,慢指针正好指向待删除节点的前一个节点。 3. **链表相交**:先计算两链表长度并调整起点,确保从相同距离末尾的位置开始遍历,从而高效找到相交节点或确定无交点。 以上方法均在时间复杂度和空间复杂度上进行了优化,适合用于理解和掌握链表的基本操作及常见算法设计思路。
LeetCode第二十四题(两两交换链表中的节点)
这篇文章介绍了LeetCode第24题的解法,即如何通过使用三个指针(preNode, curNode, curNextNode)来两两交换链表中的节点,并提供了详细的代码实现。
156 0
LeetCode第二十四题(两两交换链表中的节点)
Leetcode第十九题(删除链表的倒数第N个节点)
LeetCode第19题要求删除链表的倒数第N个节点,可以通过快慢指针法在一次遍历中实现。
203 0
Leetcode第十九题(删除链表的倒数第N个节点)
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
182 0
|
存储 算法 安全
HashMap常见面试题(超全面):实现原理、扩容机制、链表何时升级为红黑树、死循环
HashMap常见面试题:红黑树、散列表,HashMap实现原理、扩容机制,HashMap的jd1.7与jdk1.8有什么区别,寻址算法、链表何时升级为红黑树、死循环
|
存储 SQL 算法
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
|
存储 SQL 算法
LeetCode 题目 86:分隔链表
LeetCode 题目 86:分隔链表
|
存储 算法 Java
【经典算法】Leetcode 141. 环形链表(Java/C/Python3实现含注释说明,Easy)
【经典算法】Leetcode 141. 环形链表(Java/C/Python3实现含注释说明,Easy)
161 2
<数据结构>五道LeetCode链表题分析.环形链表,反转链表,合并链表,找中间节点.
<数据结构>五道LeetCode链表题分析.环形链表,反转链表,合并链表,找中间节点
226 1