例题描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
示例:
- 输入:
1->2->3->3->4->4->5
- 输出:
1->2->5
结点结构体定义
struct ListNode {
int val;
struct ListNode *next;
};
解题思路
- 处理极端情况, 如果传入的链表为空,或者只有一个结点,都不存在有重复的结点,所以选择直接返回链表。
- 创建四个指针:
①一个前驱指针pPre
,初始化为链表的头指针pHead
。用来标记当前指针的前一个结点,删除重复元素时可以使用到。
②一个当前指针pCur
,初始化为链表的头指针pHead
。用来作为循环判定的因子。
③一个pFirst
指针,初始化为空(NULL
)。
④一个pLast
指针,初始化为空(NULL
)。
● 使用pFirst
和pLast
来标记重复区间,左闭右开,左删右不删。
- 大体处理逻辑:先找重复结点区间,再删除重复结点。
- ------------------------------------------ 开始查找重复结点区间 ------------------------------------------
- 创建循环,判定因子为
pCur
,如果当前指针无效,即指向了链表的尾部NULL
,退出循环。否则开始之后逻辑。 - 使
pFirst
指针指向当前指针pCur
,使pLast
指针指向当前指针的下一结点pCur->next
。
此时判断pLast
指针是否为空,如果为空说明pFirst
指针之后没有结点了,此时pFirst
指针就指向的是最后一个结点。不为空则判定pCur
指向结点值与pLast
指向结点值是否相等,如果不等,说明没有区间内重复,直接跳出。如果相等,继续检查后面的结点(通过pCur
后移一步,pLast
后移一步,进行下次循环)。
【题设中此时pLast
指针指向空,pLast
指向结点值与pCur
并不相等,所以执行循环跳出逻辑】 - 循环结束后进行条件判定,
pFirst
的next
是否为pLast
,因为pCur
在上个while循环中有可能会移动
< 如果 >:此时相等,表示其没有移动。此时更新pPre
和pCur
指针,使它们都后移一个单位,进行下面的结点判定。
< 如果 >:此时不相等,说明在[pFirst,pLast)
区间中存在重复的元素,那么就要删除所有重复的元素,包括此时的pFirst
所指向的结点,进行的是头删操作。
- ------------------------------------------ 开始删除区间中重复结点 ------------------------------------------
- 此时删除结点还要分情况讨论:
< 如果 >:pFirst
和头结点pHead
指向相同,即要删除的结点是头结点时,我们选择改变头指针的方式处理。因为重复的可能有很多结点,所以我们选择遍历其中的结点进行步进,while
循环的判定条件是pHead != pLast
,如果满足条件就将pHead
指向它的下一结点,并释放掉pFirst
结点的空间(不必要)。再让pFirst
指向pHead
,相当于pFirst
指针也往后走了一步。进行下一次循环。当pHead
与pLast
指向相同时跳出循环,此时更新后的pHead
指向了不包含之前判断的重复元素的结点。
< 如果 >:不是头结点,创建一个新指针pDel
,将它的指向初始化为pFirst
,它的作用是标记下pFirst改变之前的结点指向。进行循环,条件是pFirst
和pLast
不为同一指向,如果指向相同时,跳出循环,此时pFirst
指针指向了不包含之前判断的重复元素的结点。 - 头删逻辑完成,此时进行链表断点的链接,将
pPre
的next
指向pLast
的指向,这样就完成了断结点的链接,此时更新当前指针pCur
的指向到pLast
,进行新一轮pCur
结点循环判定。当其指向空时,当前指针到达表尾,处理完了所有结点,循环退出。 - 最终返回处理完毕的原链表头指针
pHead
即可。
代码实现
ListNode* deleteDuplication(ListNode* pHead) {
//空链表 或 只有一个结点,没有重复的结点,直接返回
if (pHead || pHead->next) {
return pHead;
}
ListNode *pPre = pHead;
ListNode *pCur = pHead;
ListNode *pFirst = NULL;
ListNode *pLast = NULL;
//************************** 1. 找重复连续结点的区间 **************************
while (pCur) {
pFirst = pCur;
pLast = pCur->next;
while (pLast) { //pLast不为空说明pfirst之后还有结点
if (pCur->val != pLast->val) { //没有重复,跳出
break;
}
else { //如果pFirst和pLast相等,继续检查后面的
pCur = pCur->next;
pLast = pLast->next;
}
}
//如果[ pFirst , pLast )区间中没有重复的元素,break跳出情况
if (pFirst->next == pLast) { //因为pCur在上个while循环中可能会移动,如果相等表示pCur没有移动
pPre = pCur;
pCur = pLast; //后移一个单位,进行下次循环
}
//************************** 2. 删除重复结点(头删) **************************
else { //说明[ pFirst , pLast )区间中有重复的元素,break跳出为中间结点重复停止,while循环结束跳出为整个链表都是重复元素情况,到达NULL重复停止
//2.1 如果是头结点的情况,要改变头指针
if (pFirst == pHead) {
while (pHead != pLast) {
pHead = pHead->next;
free(pFirst);
pFirst = pHead; //pFirst往后走一步
}
}
//2.2 不是头结点的情况
else {
ListNode *pDel = pFirst;
while (pFirst != pLast) {
pFirst = pFirst->next;
free(pDel);
pDel = pFirst;
}
//链接断点
pPre->next = pLast;
pCur = pLast;
}
}
}
return pHead;
}
图文演示
- 设置前驱指针与当前指针的指向。
- 此时
pCur
不为空,进入while
循环逻辑,初始化pFirst
与pLast
的指向。
- 此时
pLast
不为空,比较当前指针pCur
结点值与pLast
结点值,发现二者不相等,退出此层循环。 - 循环结束后,此时
pFirst
指针的下一个结点为pLast
,if
条件满足,所以前驱指针pPre
指向当前指针,当前指针pCur
更改指向为pLast
,注意二者的顺序不能调换。进行下一次循环。
【第一次循环结束图】:
- 进行下一次循环,
while
循环判定当前指针pCur
是否为空,此时不为空,所以更新pFirst
和pLast
指向。
- 此时判定
pLast
有效性,此时指向有效结点不为空,进入循环,pCur
结点值仍然和pLast
结点值不相同,跳出循环。 - 我们发现
pFirst
的下一结点为pLast
,重复第一次的逻辑,更新前驱指针与当前指针。
- 此时
pCur
不为空,更新pCur
与pLast
指针,此时我们发现与前两次循环不同的是,此时pFirst
与pLast
指针指向的结点值是相等的,这两个是连续重复的结点。此时pLast
指向结点有效,进入循环。
- 此时两结点值相等,执行
else
逻辑,使用当前指针pCur
与pLast
进行右侧区间划分。当前指针向后走一步,pLast
也向后走一步,重复此次循环,将所有重复值包含在[pFirst,pLast)
区间中。当pCur
与pLast
指向结点值不同时,循环逻辑结束。此时pLast
作为右边界。
if
条件判定,因为pCur
指针在前面while
循环中改变了,即pFirst->next != pLast
,所以if
条件不满足,执行else
逻辑。此时处于不是头结点的情况,我们选择头删的处理方式删除重复结点,- 新建指针
pDel
指向pFirst
,此时这个结点是要删除的第一个结点。使用循环将[pFirst,pLast)
之间所有重复结点逐个删除,释放内存空间,最后删除完毕,跳出循环,此时pFirst
与pLast
指针指向同一结点。
- 此时存在以删除结束的结点为界限的两个链表,为了保证链表的完整性,我们执行链表断点的链接逻辑。此时前驱结点终于派上用场了,使用
pPre
将断点处“缝合”起来。通过尾插,让前驱指针pPre
的next
指向pLast
,完成链接,然后更新当前指针pCur
指向pLast
指向的结点。
【此次循环结束,剔除了第一次出现的重复结点】
- 进行下一次逻辑,大致与第三次循环相同,这里同理操作,展示一下第四次循环结束后的情况。
- 第五次循环结束情况。
- 循环最终结束于第六次循环的入口,判定情况不满足,因为此时当前指针
pCur
已经指向了表尾,即NULL
,此时返回处理之后链表的头指针pHead
即可,此时链表为1 -> 2 -> 5
。
解法二
ListNode* deleteDuplication(ListNode* pHead) {
ListNode *dummy = new ListNode(-1);
ListNode *tail = dummy;
while (pHead) {
// 进入循环时,确保了 pHead 不会与上一节点相同
if (pHead->next == nullptr || pHead->next->val != pHead->val) {
tail->next = pHead;
tail = pHead;
}
// 如果 pHead 与下一节点相同,跳过相同节点(到达「连续相同一段」的最后一位)
while (pHead->next != nullptr && pHead->val == pHead->next->val) pHead = pHead->next;
pHead = pHead->next;
}
tail->next = nullptr;
return dummy->next;
}