移除链表元素
LeetCode题号:203
所属类型:链表
题目
说明
给你一个链表的头节点head和一个整数val,请你删除链表中所有满足Node.val == val的节点,并返回新的头节点。
示例
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
输入:head = [], val = 1
输出:[]
输入:head = [7,7,7,7], val = 7
输出:[]
提示
- 列表中的节点数目在范围 [0, 104] 内
- 1 <= Node.val <= 50
- 0 <= val <= 50
题解
- 链表节点
struct ListNode
{
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
方法1:常规方法
class Solution
{
public:
ListNode* removeElements(ListNode* head, int val)
{
ListNode* pPre = nullptr;
ListNode* pCur = head;
// 遍历链表,每个节点的值与目标值进行比对
while (nullptr != pCur)
{
if (pCur->val == val)
{
pCur = DeleteNode(head, pPre, pCur);
}
else
{
pPre = pCur;
pCur = pCur->next;
}
}
return head;
}
/**
*@brief 删除节点函数,并返回被删除节点的下一个节点
*/
ListNode* DeleteNode(ListNode*& pHead, ListNode* pPre, ListNode* pDelete)
{
// 获取被删除节点的下一个节点
ListNode* pNext = pDelete->next;
// 当pPre为nullptr时,实则删除的节点就是头结点
nullptr == pPre ? pHead = pNext : pPre->next = pNext;
delete pDelete;
return pNext;
}
};
- 方法分析:
封装了一个删除节点的函数,函数的传入参数有三个
- ListNode*& pHead:头结点指针引用 ,目的是在删除节点为头结点时更新头结点
- ListNode* pPre :删除节点的前驱节点
- ListNode* pDelete:删除节点
返回值为被删除节点的后一个节点
所以我们只需要对链表进行遍历,让每个节点的值域与目标值进行比对,相同则调用删除函数,不同则进行下一个节点的比对。
方法2:虚拟头节点
class Solution
{
public:
ListNode* removeElements(ListNode* head, int val)
{
// 创建一个虚拟头结点
ListNode* pDummyHead = new ListNode(0, head);
ListNode* pTemp = pDummyHead;
// 采用 pTemp->next 的方式实际时处理的 pTemp 的下一个节点
while (nullptr != pTemp->next)
{
if (pTemp->next->val == val)
{
// 获取删除节点
ListNode* pDelete = pTemp->next;
// 将被删除的节点跳过
pTemp->next = pTemp->next->next;
delete pDelete;
}
else
{
pTemp = pTemp->next;
}
}
head = pDummyHead->next;
delete pDummyHead;
return head;
}
};
- 方法分析:
创建了一个虚拟的头节点pDummyHead,将遍历指针指向虚拟头节点,开始遍历整个链表。当pTemp指向pDummyHead时,我们处理的实际上是pTemp的后继节点,当pTemp的位置位于链表倒数第二个节点时,其next为最后一个节点,此时处理最后一个节点,当pTemp指向最后一个节点时,此时的后继节点为空,处理完成。这个方法的好处是不需要变量存储被删除节点的前驱节点。所有的节点都可以统一方式处理。
方法3:采用C++ STL库处理
class Solution
{
public:
ListNode* removeElements(ListNode* head, int val)
{
vector<ListNode*> vecTempNode; // 不删除的节点
vector<ListNode*> vecDeleteNode; // 删除的节点
ListNode* pCur = head;
while (nullptr != pCur)
{
pCur->val == val ? vecDeleteNode.push_back(pCur) : vecTempNode.push_back(pCur);
pCur = pCur->next;
}
// 释放删除的节点内存
Release(vecDeleteNode);
// 连接不删除的节点构成新链表
head = LinkVecNode(vecTempNode);
return head;
}
// 释放vector里面的节点
void Release(vector<ListNode*>& vecNode)
{
if (vecNode.empty())
return;
for (size_t i = 0; i != vecNode.size(); ++i)
{
if (nullptr != vecNode[i])
delete vecNode[i];
}
vecNode.clear();
}
ListNode* LinkVecNode(vector<ListNode*>& vecNode)
{
if (vecNode.empty())
return nullptr;
// 找到头节点
ListNode* pHead = vecNode[0];
ListNode* pTemp = pHead;
for (size_t i = 1; i != vecNode.size(); ++i)
{
// 方法一
pTemp->next = vecNode[i];
// 方法二:做一次判断,再看用不用赋值,因为有可能有些节点本身就是连接状态的
/*
if (pTemp->next != vecNode[i])
pTemp->next = vecNode[i];
*/
pTemp = pTemp->next;
}
// 此时pTemp指向的是最后一个节点
// 一定要将其next置位空,否则next可能指向已经被删除的节点
pTemp->next = nullptr;
return pHead;
}
};
- 方法分析:
采用了两个vector<ListNode*>分别存储不用删除的节点和需要删除的节点,然后清空需要删除节点的内存,再链接不被删除的节点,从而构成题目所需的链表,要注意的是在链接不被删除的链表时,一定要将最后一个节点的next指针域值为空,否则可能出错,比如:
【1, 2, 3, 4, 5】 目标删除值为5时,当进行每个节点的分类后,再链接不被删除的节点时最后一个节点是4,但此时它的next指针域并不为空,而是继续指向了被删除的节点5,所以需要手动将4的next置位空。