【图文解析】删除链表中连续重复的结点


例题描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。

示例:

  • 输入:1->2->3->3->4->4->5
  • 输出:1->2->5

结点结构体定义

struct ListNode {
	int val;
	struct ListNode *next;
};

解题思路

  1. 处理极端情况, 如果传入的链表为空,或者只有一个结点,都不存在有重复的结点,所以选择直接返回链表。
  2. 创建四个指针:
    ①一个前驱指针pPre,初始化为链表的头指针pHead。用来标记当前指针的前一个结点,删除重复元素时可以使用到。
    ②一个当前指针pCur,初始化为链表的头指针pHead。用来作为循环判定的因子。
    ③一个pFirst指针,初始化为空(NULL)。
    ④一个pLast指针,初始化为空(NULL)。
    ● 使用pFirstpLast来标记重复区间,左闭右开,左删右不删。
    在这里插入图片描述
  3. 大体处理逻辑:先找重复结点区间,再删除重复结点
  • ------------------------------------------ 开始查找重复结点区间 ------------------------------------------
  1. 创建循环,判定因子为pCur,如果当前指针无效,即指向了链表的尾部NULL,退出循环。否则开始之后逻辑。
  2. 使pFirst指针指向当前指针pCur,使pLast指针指向当前指针的下一结点pCur->next
    此时判断pLast指针是否为空,如果为空说明pFirst指针之后没有结点了,此时pFirst指针就指向的是最后一个结点。不为空则判定pCur指向结点值与pLast指向结点值是否相等,如果不等,说明没有区间内重复,直接跳出。如果相等,继续检查后面的结点(通过pCur后移一步,pLast后移一步,进行下次循环)。
    【题设中此时pLast指针指向空,pLast指向结点值与pCur并不相等,所以执行循环跳出逻辑】
  3. 循环结束后进行条件判定,pFirstnext是否为pLast,因为pCur在上个while循环中有可能会移动
    < 如果 >:此时相等,表示其没有移动。此时更新pPrepCur指针,使它们都后移一个单位,进行下面的结点判定。
    < 如果 >:此时不相等,说明在[pFirst,pLast)区间中存在重复的元素,那么就要删除所有重复的元素,包括此时的pFirst所指向的结点,进行的是头删操作。
  • ------------------------------------------ 开始删除区间中重复结点 ------------------------------------------
  1. 此时删除结点还要分情况讨论:
    < 如果 >:pFirst和头结点pHead指向相同,即要删除的结点是头结点时,我们选择改变头指针的方式处理。因为重复的可能有很多结点,所以我们选择遍历其中的结点进行步进,while循环的判定条件是pHead != pLast,如果满足条件就将pHead指向它的下一结点,并释放掉pFirst结点的空间(不必要)。再让pFirst指向pHead,相当于pFirst指针也往后走了一步。进行下一次循环。当pHeadpLast指向相同时跳出循环,此时更新后的pHead指向了不包含之前判断的重复元素的结点。
    < 如果 >:不是头结点,创建一个新指针pDel,将它的指向初始化为pFirst,它的作用是标记下pFirst改变之前的结点指向。进行循环,条件是pFirstpLast不为同一指向,如果指向相同时,跳出循环,此时pFirst指针指向了不包含之前判断的重复元素的结点。
  2. 头删逻辑完成,此时进行链表断点的链接,将pPrenext指向pLast的指向,这样就完成了断结点的链接,此时更新当前指针pCur的指向到pLast,进行新一轮pCur结点循环判定。当其指向空时,当前指针到达表尾,处理完了所有结点,循环退出。
  3. 最终返回处理完毕的原链表头指针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;
}

图文演示

  1. 设置前驱指针与当前指针的指向。
    在这里插入图片描述
  2. 此时pCur不为空,进入while循环逻辑,初始化pFirstpLast的指向。
    在这里插入图片描述
  3. 此时pLast不为空,比较当前指针pCur结点值与pLast结点值,发现二者不相等,退出此层循环。
  4. 循环结束后,此时pFirst指针的下一个结点为pLastif条件满足,所以前驱指针pPre指向当前指针,当前指针pCur更改指向为pLast,注意二者的顺序不能调换。进行下一次循环。
    【第一次循环结束图】:
    在这里插入图片描述
  5. 进行下一次循环,while循环判定当前指针pCur是否为空,此时不为空,所以更新pFirstpLast指向。
    在这里插入图片描述
  6. 此时判定pLast有效性,此时指向有效结点不为空,进入循环,pCur结点值仍然和pLast结点值不相同,跳出循环。
  7. 我们发现pFirst的下一结点为pLast,重复第一次的逻辑,更新前驱指针与当前指针。
    在这里插入图片描述
  8. 此时pCur不为空,更新pCurpLast指针,此时我们发现与前两次循环不同的是,此时pFirstpLast指针指向的结点值是相等的,这两个是连续重复的结点。此时pLast指向结点有效,进入循环。
    在这里插入图片描述
  9. 此时两结点值相等,执行else逻辑,使用当前指针pCurpLast进行右侧区间划分。当前指针向后走一步,pLast也向后走一步,重复此次循环,将所有重复值包含在[pFirst,pLast)区间中。当pCurpLast指向结点值不同时,循环逻辑结束。此时pLast作为右边界。
    在这里插入图片描述
  10. if条件判定,因为pCur指针在前面while循环中改变了,即pFirst->next != pLast,所以if条件不满足,执行else逻辑。此时处于不是头结点的情况,我们选择头删的处理方式删除重复结点,
  11. 新建指针pDel指向pFirst,此时这个结点是要删除的第一个结点。使用循环将[pFirst,pLast)之间所有重复结点逐个删除,释放内存空间,最后删除完毕,跳出循环,此时pFirstpLast指针指向同一结点。
    在这里插入图片描述
  12. 此时存在以删除结束的结点为界限的两个链表,为了保证链表的完整性,我们执行链表断点的链接逻辑。此时前驱结点终于派上用场了,使用pPre将断点处“缝合”起来。通过尾插,让前驱指针pPrenext指向pLast,完成链接,然后更新当前指针pCur指向pLast指向的结点。
    【此次循环结束,剔除了第一次出现的重复结点】
    在这里插入图片描述
  13. 进行下一次逻辑,大致与第三次循环相同,这里同理操作,展示一下第四次循环结束后的情况。
    在这里插入图片描述
  14. 第五次循环结束情况。
    在这里插入图片描述
  15. 循环最终结束于第六次循环的入口,判定情况不满足,因为此时当前指针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;
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

giturtle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值