82. Remove Duplicates from Sorted List II(删除排序链表中的重复元素 II)两种解法(C++ & 注释)

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. [1,2,3];- 都为不重复节点
  2. [1];- 为不重复节点
  3. [1,1,1] - 为重复节点
  4. [1,1,2] - 2为不重复节点
  5. [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.的条件:

  1. pre->val != cur->val && cur->val != cur->next->val;
  2. !pre && !cur->next;
  3. !cur->next && pre->val != cur->val;
  4. !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 解题思路

接下来我们使用递归的方法来解答本题,思路也不难。我们先来回想一下上面迭代是怎么删除重复节点的:

  1. 不重复的节点,其next指向下一个不重复节点;
  2. 跳过重复节点,寻找后面不重复节点;

所以根据上面的思路总结,递归代码也非常好理解啦:

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);
    }
};

4. 参考资料

  1. O(N) time O(1) space, Easiest Understanding
  2. My Recursive Java Solution

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值