剑指offer 学习笔记 删除链表的节点
面试题18:删除链表的节点。
(1)在O(1)的时间内删除链表节点。给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点,链表节点定义如下:
struct ListNode {
int m_nValue;
ListNode *m_pNext;
};
该问题的常规做法是从头节点开始,顺序遍历查找要删除的节点,并在链表中删除该节点。这种思路需要顺序查找,时间复杂度为O(n)。
删除一个节点并不需要遍历整个链表,只需要将当前要删除的节点内容换成的下一个节点的内容,再把下一个节点删除就好了。但如果要删除的节点是链表尾部的节点,我们就只能从链表头开始遍历直到该节点的前序节点再删除它了。最后还有一点,当链表中只有一个头节点,并且我们要删除它,在删除之后,还要将头结点设置为nullptr:
void DeleteNode(ListNode** pHead, ListNode* pToBeDeleted) {
if (pHead == nullptr || *pHead == nullptr || pToBeDeleted == nullptr) {
return;
}
if (pToBeDeleted->m_pNext != nullptr) { // 如果要删除的节点不是尾节点
ListNode* pNode = pToBeDeleted->m_pNext;
pToBeDeleted->m_nValue = pToBeDeleted->m_pNext->m_nValue;
pToBeDeleted->m_pNext = pToBeDeleted->m_pNext->m_pNext; // 将要删除的节点内容替换成下一个节点内容
delete pNode; // 删除下一个节点,但指针没有被删除
pNode = nullptr; // 使指向被删除节点的指针变为空指针,否则该指针还是指向被删除对象的地址,不安全
} else if (pToBeDeleted == *pHead) { // 要删除的是只有头节点的链表
delete pToBeDeleted; // 直接删除头结点
pToBeDeleted = nullptr;
*pHead = nullptr; // 删除头节点后,还需让指向头节点的指针的值变为nullptr
} else { // 要删除的节点位于尾部且不是头节点
ListNode* pNode = *pHead;
while (pNode->m_pNext != pToBeDeleted) {
pNode = pNode->m_pNext;
}
pNode->m_pNext = nullptr;
delete pToBeDeleted;
pToBeDeleted = nullptr;
}
}
(2)删除链表中重复的节点。在一个排序的链表中,删除重复的所有节点。
首先头结点也可能与后面的节点相同,因此函数定义时头结点应该表示为头结点指针的指针。接下来从头遍历整个链表,如果当前节点与下一个节点值相同,那么它们就是重复的节点,都要被删除。为保证删除之后的链表仍然是相连的,我们要把当前节点的前一个节点和后边值比当前节点的值大的节点相连:
#include <iostream>
#include <stack>
using namespace std;
struct ListNode {
int m_nValue;
ListNode* m_pNext;
};
void AddToTail(ListNode** pHead, int value) {
ListNode* pNew = new ListNode(); // 为新结点分配内存
pNew->m_nValue = value;
pNew->m_pNext = nullptr;
if (*pHead == nullptr) { // 如果是空链表
*pHead = pNew;
}
else {
ListNode* pNode = *pHead;
while (pNode->m_pNext != nullptr) { // 找到链表末尾
pNode = pNode->m_pNext;
}
pNode->m_pNext = pNew; // 将链表末尾元素的指针指向新节点
}
}
void printListReversingly_iteratively(ListNode* pHead) {
stack<ListNode*> nodes;
ListNode* pNode = pHead;
while (pNode != nullptr) { // 将链表从头到尾压栈
nodes.push(pNode);
pNode = pNode->m_pNext;
}
while (!nodes.empty()) { // 打印栈中内容
cout << nodes.top()->m_nValue << endl; // 打印栈顶值
nodes.pop(); // 栈顶元素出栈
}
}
void DeleteDplication(ListNode** pHead) {
if (pHead == nullptr || *pHead == nullptr) {
return;
}
ListNode* preNode = nullptr;
ListNode* pNode = *pHead;
while (pNode) {
ListNode* pNext = pNode->m_pNext;
bool isDup = false; // 当前节点值是否与下一节点值相等
if (pNext && pNode->m_nValue == pNext->m_nValue) { // 如果是
isDup = true;
}
if (!isDup) { // 不用删除当前节点时
preNode = pNode; // 将当前节点设为preNode
pNode = pNode->m_pNext; // 后移一位
} else { // 需要删除当前节点时
int value = pNode->m_nValue; // 记录当前节点的值
ListNode* toBeDel = pNode; // 要删除的节点
while (toBeDel != nullptr && toBeDel->m_nValue == value) { // 当要删除的节点不为空且该节点与要删除到的节点值相同时,则删除
pNext = toBeDel->m_pNext; // 先将pNext指向要删除的节点之后
delete toBeDel; // 删除该节点
toBeDel = pNext; // 验证下一个节点是否应该删除
}
if (preNode == nullptr) { // 如果preNode的值还为空,说明刚刚删除的是头结点
*pHead = pNext; // 令头结点的值为最后一个删除的节点的后一个结点
} else { // 如删除的不是头结点
preNode->m_pNext = pNext; // 令第一个删除的节点之前的节点与最后一个删除的节点之后的节点连接起来
}
pNode = pNext; // 从最后删除的节点的后一个节点开始继续循环查找
}
}
}
int main() {
ListNode* pHead = new ListNode();
pHead->m_nValue = 0;
pHead->m_pNext = nullptr;
AddToTail(&pHead, 0);
AddToTail(&pHead, 2);
AddToTail(&pHead, 3);
AddToTail(&pHead, 3);
AddToTail(&pHead, 5);
AddToTail(&pHead, 7);
AddToTail(&pHead, 7); // 向链表中插入节点
DeleteDplication(&pHead); // 删除重复值
printListReversingly_iteratively(pHead); // 打印验证
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2019-02-21 JAVA分支结构