Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only
distinct numbers from the original list.
For example,
Given 1->2->3->3->4->4->5, return 1->2->5.
Given 1->1->1->2->3, return 2->3.
我的思路是对于链表的某一节点,先找出这个节点后的重复元素,再将这些重复元素删除,接着对这个链表进行遍历。这种先将重复元素查询出来再将其删除的想法需要使用三个指针,并且对于链表头中就有重复元素和链表中间存在重复元素的处理方法也不同。
先考虑链表头中就存在重复元素情况:[1,1,1,2,3]:
使用三个指针first,second,third。first初始指向第一个元素,second和thrid指向第二个元素。
对于链表头中就存在重复元素的情况只需要使用到两个指针,这里使用first和second
比较first和second指向的元素是否相等,相等的话就将second不断右移,直到找到第一个和first指向元素不等的元素。如下图:
然后再将[first,second)之间的元素删除,并使first指向second相同的元素。此时如果first为空,表示链表中后面不存在元素了,将second和third都置为NULL,如果first不为空,将second和third向前移动一位。接着可以返回while循环再进行判断。之所以用while不用if是为了处理这样的测试用例:[1,1,2,2,3]。
上面是对链表头中存在重复元素的处理。
处理完后就能保证first指向的元素和second(second和third初始指向同一元素)指向的元素的值不同了。这时重点就是判断second后面如果存在重复元素的话会有多少个,这是third指针就派上用场了。这个指针用来指向最后一个和second指针指向节点相同值的节点,对于[1,2,2,2,3]这个测试用例而言:
third初始和second指向同一元素,即first后面一个节点,由于second后面存在和second元素值相同的节点,所以third指针一直后移到最后一个和second元素值相同的节点。此时将first的下一个指针指向thrid的下一个节点,然后就可以将second和third之间的元素删除了。再更新second和third的位置即可。
C++代码实现:
runtime:11ms
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head==NULL) return NULL;
//使用三个指针,第一个指针指向没有重复的最后一个一个元素,第二个指针和第三个指针标示一段数字重复的区域
//初始时第一个指针first指向链表头,第二个second和第三个second指针指向第二个元素
ListNode *first=head;
ListNode *second=head->next;
ListNode *third=head->next;
//判断遍历时是否有重复的变量
bool repeat=false;
//对于数组中最前面有重复元素时需要进行特殊处理
//此时由于需要比较first和second指向值是否相等,所以需要在前面对它们两个进行非空判断
//处理这样的测试用例[1,1,2,2,3]
while(first!=NULL&&second!=NULL&&first->val==second->val)
{
//如果最前面第一个元素和第二元素相等,需要继续先后查询直到找到第一个不等的元素,second此时指向它
while(first!=NULL&&second!=NULL&&first->val==second->val)
second=second->next;
//然后开始删除元素,一直到second,此时first指针和second指针指向相同的节点
ListNode *tmp=first;
while(tmp!=second)
{
first=first->next;
delete tmp;
tmp=first;
}
//接着对上面的first和second指向的节点进行判断,如果它非空,那么second和third向后移动一位
if(first!=NULL)
{
second=first->next;
third=second;
}
else//如果这个节点为空,表示后面已经不存在元素了,此时将second和third指针都置为NULL
{
second=NULL;
third=NULL;
}
}
//上面的循环结束后就能确定删除元素后的链表的头指针了,将它保存起来。
head=first;
//然后开始对链表进行遍历
while(third!=NULL)
{
//判断first指针后面是否存在重复元素,将第三个指针的下一个元素与第二个指针指向的元素比较,
//直到找到不同的元素,这时second元素指向重复元素的第一个元素,
//third指向重复元素的最后一个元素,并记录下first元素后面是否存在重复元素
while(third!=NULL&&third->next!=NULL&&second->val==third->next->val)
{
third=third->next;
repeat=true;
}
//如果first后面存在重复元素,那么需要删除second和third指针指向的中间的元素。
if(repeat)
{
//首先将first的指针指向third的下一个元素,此时这个元素必定了second和third中间的元素不同
first->next=third->next;
//开始删除[second,third]之间的元素,包括second和third
ListNode *tmp=second;
while(second!=third->next)
{
second=second->next;
delete tmp;
tmp=second;
}
//然后将second的指针向后移动一位,并保持second和third相同
second=first->next;
third=second;
//处理完重复元素后需要将判断重复元素的变量重新设置为false
repeat=false;
}
else//如果first后面的元素不是重复元素,那么将first加1,将second和third同时指向后一个元素
{
first=second;
second=second->next;
third=second;
}
}
return head;
}
};
在Discuss中见到好多使用一个伪节点的方法,使用这个伪节点可以将上面链表头中就存在重复元素和中间存在重复元素统一到一起,这样可以减少大量的代码。而且可以将查询重复的元素与删除操作合并到一起(其实先查询再删除是一种很蠢的办法,以后不要犯这种傻了)。这样代码就变得很精练了。
注意下面fakeNode节点就是伪节点,prev指针指向当前节点的前一个非重复节点。根据prev的含义,可以发现prev初始就是fakeNode节点。因为fakeNode节点初始指向head节点。
runtime:8ms
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode * fakeNode=new ListNode(0);
fakeNode->next=head;
ListNode *prev=fakeNode;
while(head!=NULL)
{
if(head->next&&head->val==head->next->val)
{
int value=head->val;
while(head&&head->val==value)
{
prev->next=head->next;
delete head;
head=prev->next;
}
}
else
{
prev=head;
head=head->next;
}
}
return fakeNode->next;
}
};