给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
方法一:用一个工人指针遍历链表,比较前后两个元素是否相等
正常的逻辑输出其实就是这个思路,之前在LeetCode - 206 - 反转链表里谈过关于如何利用一前一后的的指针和头指针做题,而在本题中这两样其实都淡化了。
首先我用一个"工人指针"p (这个概念我在之后的博客中会详细谈到) “附魔"进入链表”。我也不知道我为什么会想到这么中二的两个名字来命名我的方法…附魔其实就是派一个指针去替我遍历完整个链表。
ListNode *p = head;
其次我为了保证我前后的val值可以进行比较,我就要保证p->val 和 p->next->val都要存在。故我们可以得出让我的这个工人指针持续跑动的要求为 p != nullptr && p->next != nullptr。这样我才可以保证p->next->val一定存在
while ( p && p->next )
最后非常容易了,我们在循环中进行比较,如果p->val == p->next->val 则进行删除操作,不然就让p持续移动
if ( p->val == p->next->val )
p->next = p->next->next;
else
p = p->next;
于是完整的代码便自然而然的形成了:
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if ( !head || !head->next )
return head;
ListNode *p = head;
while ( p && p->next ) {
if ( p->val == p->next->val )
p->next = p->next->next;
else
p = p->next;
}
return head;
}
};
当然,使用一前一后的pre和cur也可以解这道题,你不妨一试。那样的模板大多数时候都是没有错误的!
另外还要注意一点,这里我的删除操作p->next = p->next->next删除操作没有对被删除结点进行free操作,C++这种没有垃圾回收机制的语言这里还是需要稍微注意一下的,但我第一次写的时候也忘了也没报错,所以这里就先抛开不管了8…
这是肯定会有小萌新有疑问,哎,你这个人真是多此一举,非搞个什么工人指针出来装大牌,直接使用head中进行比较不可以嘛?
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if ( !head || !head->next )
return head;
while ( head && head->next ) {
if ( head->val == head->next->val )
head->next = head->next->next;
else
head = head->next;
}
return head;
}
};
结果控制台验证一看,惊呼,哎,前面重复的元素怎么一个都没保留下来???
我们不妨来看看下面这两幅图
结合上面两张图我们不难发现,head是链表首个元素,故每一次删除操作都是从头开始进行的,而当有head->val != head->next->val的情况是,head变成
head->next,将链表进行了截断,把链表头移动到了后面,不再是原来的那个元素!最终返回的是截断后的链表,所以有元素丢失也不为怪。这个现象我称之为"头部丢失"。而当我使用一个工人指针,这个工人完整展现了这个链表到底是个什么样子,操作不会影响链表本身的结构,所以使用工人指针是一个非常安全的做法。
最后再讲一点,我之前所推荐大家用一个头结点在这道题有个不太行的地方。
ListNode *dummyHead = new ListNode(-1);
dummyHead->next = head;
ListNode *p = dummyHead;
while ( p && p->next ) {
if ( p->val == p->next->val ) {
// 当p = dummyHead,p->next = head时,也许就会出现dummyHead->val == head-val的情况,会造成不必要的删除
// ...
}
}
小声说一句:我在当初这段代码报错的时候机智的把头结点值赋成了-65535(/ emoji funny)
方法二:递归
这个方法本身我没什么发言权,递归本来就不太熟,参考了一个dalao的解法,自己加了点自己的理解:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
// Base Situation
if ( head == null || head.next == null )
return head;
head.next = deleteDuplicates(head.next); // 已处理好的链表部分就是head.next
if ( head.val == head.next.val )
head = head.next; // 如果前后元素相等,就吞掉,递归只需要关注本级递归需要做什么
return head;
}
}
写递归除了理解模板完还要有点灵性,我愚钝,也没有太好的解释,我在刷链表的递归题中,经常就把整个链表看成某个结点,某个结点.next以及待处理的链表结点三部分,还挺管用的。