中等题 删除链表中重复的节点

本题来自:删除链表中重复的结点_牛客题霸_牛客网 (nowcoder.com)

目录

题面:

示例1

示例2


题面:

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5

数据范围:链表长度满足 0≤n≤1000 ,链表中的值满足 1≤val≤1000

进阶:空间复杂度 O(n) ,时间复杂度 O(n)

例如输入{1,2,3,3,4,4,5}时,对应的输出为{1,2,5},对应的输入输出链表如下图所示:

示例1

输入:{1,2,3,3,4,4,5}

返回值:{1,2,5}

示例2

输入:{1,1,1,8}

返回值:{8}

代码:

objectivec 复制代码
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param pHead ListNode类 
 * @return ListNode类
 */
typedef struct ListNode node;

struct ListNode* deleteDuplication(struct ListNode* pHead)
{
	node* prev = NULL; // 指向pHead的头节点
	node* cur = NULL;  // 指向pHead的第二个节点
    
	// 排除空链表和单节点链表
	if (pHead == NULL || pHead->next == NULL)
		return pHead;

	node* newhead = (node*)malloc(sizeof(node)); // 新链表的头,有哨兵位
	node* tail = NULL; // tail负责在新链表找尾节点
    
	newhead->next = NULL; // 哨兵位指向空
	prev = pHead; // prev先定位在原链表的头节点
	cur = pHead->next; // cur比prev靠后一个节点
    
	while (prev)
	{
		if (cur->val != prev->val || cur == NULL) 
                // 如果cur和prev两个节点的val不相同或cur指向尾
		{
			tail = newhead;    // tail从哨兵位开始找尾
			while (tail->next) 
            // 这里用tail的next作为循环条件,当循环停止的时候,tail就在尾节点了
			{
				tail = tail->next; // tail每次在新链表移动一个节点
			}
            // 当tail弹出来的时候,tail就是尾节点了
			tail->next = prev; // 把prev接在尾节点后面
			prev->next = NULL; // prev节点成为新的尾节点
			prev = cur; // prev指针后移
			if(prev!=NULL) // 如果prev没有到达原链表的尾,就让cur后移
			    cur = cur->next;
            // 因为当prev到尾的时候,cur本来就在尾上,已经是NULL,cur再后移就越界了
		}
		else if(cur->val==prev->val) // 如果cur和prev指向的节点val相同
		{
			while (cur->val == prev->val && cur != NULL) // 找cur和prev的val不同的节点
			{
				cur = cur->next;
			}
            // 要保证cur没有到尾,如果cur到尾了,说明prev到尾的所有节点val相同,直接全部free
			prev = cur;
            if(cur!=NULL) // 如果cur没有到尾,就继续后移并略过prev到cur的所有节点
			    cur = cur->next;
		}
	}
	return newhead->next; // 返回新节点的时候要越过哨兵位,也可以自己释放哨兵位
}

思路梳理:

在做本题时,我采用了一个比较极端的方法,这种方法可能会造成内存泄漏(虽然咱们是在解题不用考虑这些问题,但是这种方式在企业里面十分不好)

究竟是什么方法呢?继续往下看

先说说如何解题,解题的关键是什么

这里我们用双指针的方法,因为是要比较两个相邻节点的val值,如果不同就留下,相同就全都舍弃创建两个指针prev,cur来遍历原链表,这两个指针一前一后来比较节点数据

cur是从题给链表的第二个节点开始的,那么就要排除一下空链表和单节点链表的情况

prev从第一个节点开始,cur从第二个节点开始,进入循环

循环开始:用if判断prev和cur的val值,如果不相同,就把prev节点尾接到新链表,并把prev的next指向NULL,然后prev指针移动到cur指向的节点,cur后移

如果prev和cur的val相同,就让cur一直后移,找不同的那个节点,如果找到了就让prev指针也移动到cur节点,这里要判断一下prev指针是否移动到了尾节点,如果prev指向了尾,cur就不用再后移了,因为prev还指向尾节点呢,这个值是有效了,但cur已经为NULL了,说明需要让prev尾插到新链表,尾插的操作和上面的过程一样,就用"||"把这种情况也加到上面去,也就是 if (cur->val != prev->val || cur == NULL),前面是两节点不同的情况,后面就是prev为尾的情况

最后交答案的时候把哨兵位越过去就好了,觉得不太完美的就把哨兵位释放

特殊链表:

  1. {1,1,1,1,1,1}

  2. {1,1,2,2,3,3,4,4}

  3. {1,2,3,3,3,3}

针对特殊情况的解决方法:

  1. 让新链表的next指向NULL,防止一个合适的节点都无法插入到新链表

  2. 让prev,cur两个指针准确的找到符合条件的节点尾插后,及时让该节点的next置空

上面这两种情况置空后,哪怕后续没有其他节点插入,新链表也是一个完整链表

比如,针对{1,1,1,1}这种情况,我们用prev和cur判断发现prev最后指向的空,说明这个链表没有合适的节点,就不对新链表进行操作,在初始化的时候新链表就指向NULL了,哪怕没有节点插入,也不影响结果

针对{1,1,2,2,3,3,4,4},因为prev和cur一开始就定位在第一第二节点,发现cur和prev相同,就让cur后移,后移后发现值不同了,就让prev移到cur所在的节点,cur后移一个节点,然后不断重复这个过程,一直到cur指向NULL,prev也指向NULL,这个循环就结束了,新链表为空

举一个正常的例子吧,{1,2,3,3,4,4,5}

先是prev和cur定位,一上来就发现两个节点不相同,就需要把prev所指向的节点尾插到新链表,这里是先用tail找尾,因为新链表为空,newhead既是头又是尾,直接尾插就可以了,然后是2,3节点,发现2可以尾接,就用tail从newhead开始找尾,找到尾后尾插,后面是3,3,发现不合适,就让cur后移,是334,prev移到4节点,cur后移,发现44相同就cur后移445,prev后移至5,cur后移到NULL,这是特殊情况了,直接尾接,让tail重新找尾,结束

相关推荐
单片机学习之路2 分钟前
【C语言】结构
c语言·开发语言·stm32·单片机·51单片机
thesky1234562 分钟前
活着就好20241224
学习·算法
ALISHENGYA8 分钟前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战项目二)
数据结构·c++·算法
guogaocai12310 分钟前
连续自成核退火热分级(SSA)技术表征共聚聚丙烯(PP)分子链结构
算法
DARLING Zero two♡34 分钟前
【优选算法】Pointer-Slice:双指针的算法切片(下)
java·数据结构·c++·算法·leetcode
游是水里的游1 小时前
【算法day19】回溯:分割与子集问题
算法
不想当程序猿_1 小时前
【蓝桥杯每日一题】分糖果——DFS
c++·算法·蓝桥杯·深度优先
graceyun1 小时前
C语言初阶习题【9】数9的个数
c语言·开发语言
南城花随雪。2 小时前
单片机:实现FFT快速傅里叶变换算法(附带源码)
单片机·嵌入式硬件·算法