82. 删除排序链表中的重复元素 II
1. 题目描述
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
示例 2:
输入: 1->1->1->2->3
输出: 2->3
2. 迭代(Iteration)
2.1 解题思路
首先本题是:83. 删除排序链表中的重复元素的进阶版。其次,本题的关键是找到不重复节点的特点:pre->val != cur->val && cur->val != cur->next->val,即中间节点值不等于两边节点值。但是这个条件要求pre和cur->next不为空,所以我们看看这两个为空的情况下,有哪些情形是不重复节点:
- [1,2,3];- 都为不重复节点
- [1];- 为不重复节点
- [1,1,1] - 为重复节点
- [1,1,2] - 2为不重复节点
- [1,2,2] - 1为不重复节点
1.我们已经cover了,现在需要考虑一下2. ,5.和 4.,2. 是pre和cur->next都为空;4. 是cur->next为空,pre->val != cur->val;5. 是pre为空,cur->next->val != cur->val;加上1.的条件:
- pre->val != cur->val && cur->val != cur->next->val;
- !pre && !cur->next;
- !cur->next && pre->val != cur->val;
- !pre && cur->val != cur->next->val;
条件太多了,我们可以尝试取反,找到重复节点的特点:pre && pre->val == cur->val || cur->next && cur->val == cur->next->val;这里的意思是,只要前后节点有一个不为空,那么就不能等于中间节点的值,最后再取反就可以cover不重复节点的条件。
然后初始化uniqueNode = nullptr,表示当前不重复节点;newHead = nullptr,表示返回时的头结点。我们每移动pre和cur一次,如果当前节点为不重复节点,但uniqueNode为空,则uniqueNode = cur,找到第一个不重复节点,同时也是返回时的头结点;反之,则把上一个不重复节点的next指向cur,设置cur为新的不重复节点,即:uniqueNode->next = cur; uniqueNode = uniqueNode->next;
一直重复上述步骤直到cur为空,之后如果uniqueNode不为空,需要uniqueNode->next = nullptr。这是因为下面两种情况:
[1,2,3] or [1,2,2]
前者结束时,3为uniqueNode,uniqueNode->next = nullptr没有影响;后者结束时,1为uniqueNode,需要将1的next指向第二个2的next(nullptr),即uniqueNode->next = nullptr,这里是用来跳过重复节点的。这部分对应代码:2.2.1 不使用dummy pointer
如果我们直接使用pre->val != cur->val && cur->val != cur->next->val,也是可以的,只是需要保证pre和cur->next一定不为空,保证cur->next不为空可以在while循环里面添加判断条件,但是如何保证pre不为空呢?这里我们引入一个dummy pointer(假头指针,值与头指针不一样),让它指向head,pre->next = dummyNode,通过这样处理,就能保证pre不为空,余下的步骤和上面的基本一致。这部分对应代码:2.2.2 使用dummy pointer
2.2 实例代码
2.2.1 不使用dummy pointer
class Solution {
// 注意这里需要使用指针的引用,否则deleteDuplicates中的newHead和uniqueNode不会被修改
void linkNodes(ListNode*& newHead, ListNode*& uniqueNode, ListNode* cur) {
if (!newHead) newHead = cur;
if (!uniqueNode) uniqueNode = cur;
else { uniqueNode->next = cur; uniqueNode = uniqueNode->next; }
}
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* pre = nullptr, * cur = head, * newHead = nullptr, * uniqueNode = nullptr;
while (cur) {
// 下面判断cur是否为不重复的节点
if (!(pre && pre->val == cur->val || cur->next && cur->val == cur->next->val)) linkNodes(newHead, uniqueNode, cur);
pre = cur;
cur = cur->next;
}
if (uniqueNode) uniqueNode->next = nullptr;
return newHead;
}
};
2.2.2 使用dummy pointer
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) return nullptr;
ListNode dummyNode(head->val ? 0 : 1);
dummyNode.next = head;
ListNode* pre = &dummyNode, * cur = head, * uniqueNode = pre;
while (cur && cur->next) {
if (pre->val != cur->val && cur->val != cur->next->val) { uniqueNode->next = cur; uniqueNode = uniqueNode->next; }
pre = cur;
cur = cur->next;
}
// 处理末尾重复节点的情况
if (pre->val != cur->val) { uniqueNode->next = cur; uniqueNode = uniqueNode->next; }
uniqueNode->next = nullptr;
return dummyNode.next;
}
};
3. 递归(Recursion)
3.1 解题思路
接下来我们使用递归的方法来解答本题,思路也不难。我们先来回想一下上面迭代是怎么删除重复节点的:
- 不重复的节点,其next指向下一个不重复节点;
- 跳过重复节点,寻找后面不重复节点;
所以根据上面的思路总结,递归代码也非常好理解啦:
3.2 实例代码
class Solution {
ListNode* deleteDuplicates(ListNode* head, ListNode* pre) {
if (!head) return nullptr;
if (pre && pre->val == head->val || head->next && head->val == head->next->val) return deleteDuplicates(head->next, head); // 重复节点
else { head->next = deleteDuplicates(head->next, head); return head; } // 不重复节点
}
public:
ListNode* deleteDuplicates(ListNode* head) {
return deleteDuplicates(head, nullptr);
}
};