本题来自:删除链表中重复的结点_牛客题霸_牛客网 (nowcoder.com)
目录
题面:
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 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,1,2,2,3,3,4,4}
-
{1,2,3,3,3,3}
针对特殊情况的解决方法:
-
让新链表的next指向NULL,防止一个合适的节点都无法插入到新链表
-
让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重新找尾,结束