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

本题来自:删除链表中重复的结点_牛客题霸_牛客网 (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}

代码:

/**
 * 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重新找尾,结束

  • 9
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值