82. 删除排序链表中的重复元素 II

289 阅读3分钟

题目介绍

力扣82题:leetcode-cn.com/problems/re…

image.png

image.png

方法一:一次遍历

由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。由于链表的头节点可能会被删除,因此我们需要额外使用一个哑节点(dummy node)指向链表的头节点。

具体地,我们从指针 cur 指向链表的哑节点,随后开始对链表进行遍历。如果当前 cur.next 与 cur.next.next 对应的元素相同,那么我们就需要将 cur.next 以及所有后面拥有相同元素值的链表节点全部删除。我们记下这个元素值 x,随后不断将 cur.next 从链表中移除,直到 cur.next 为空节点或者其元素值不等于 x 为止。此时,我们将链表中所有元素值为 x 的节点全部删除。

如果当前 cur.next 与 cur.next.next 对应的元素不相同,那么说明链表中只有一个元素值为 cur.next 的节点,那么我们就可以将 cur 指向 cur.next。

当遍历完整个链表之后,我们返回链表的的哑节点的下一个节点 dummy.next 即可。

细节:需要注意 cur.next 以及 cur.next.next 可能为空节点,如果不加以判断,可能会产生运行错误。

代码如下:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        
        //哑节点
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode cur = dummy;
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                //记录重复节点值
                int x = cur.next.val;
                //节点值相同
                while (cur.next != null && cur.next.val == x) {
                    //删除重复节点,直到删除完
                    cur.next = cur.next.next;
                }
            } else {
                cur = cur.next;
            }
        }
        return dummy.next;
    }
}

复杂度分析

  • 时间复杂度:O(n),其中 nn 是链表的长度。
  • 空间复杂度:O(1)。

方法二:快慢指针

使用快慢指针,初始时,慢指针指向哑节点,快节点指向头节点,每次比较快指针指向的的下一个节点与慢指针指向的下一个节点值是否相同,即判断fast.next.valslow.next.val是否相等。如果不相等,则将快慢指针都向下移动,如果相等,则移动快指针,直到快指针的下一个节点值不相等,接着执行:

//将快指针的下一个节点(不包括重复节点),接到慢指针指向的节点后面
slow.next = fast.next;
//快指针后移
fast = fast.next;

image.png

image.png

image.png

image.png

image.png

image.png

代码如下:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null || head.next == null) {
            return head;
        }
        //创建虚拟节点,指向头节点
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        
        //创建快慢指针,两者指向的节点相差一
        ListNode slow = dumy;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            //如果两节点的值不相等,则继续向下移动
            if(fast.next.val != slow.next.val){
                fast = fast.next;
                slow = slow.next;
            }else{
                //如果两节点的值相等,则快指针向下移动,直到不再相等
                while(fast != null && fast.next != null && fast.next.val == slow.next.val){
                    fast = fast.next;
                }
                //将快指针的下一个节点(不包括重复节点),接到慢指针指向的节点后面
                slow.next = fast.next;
                //快指针后移
                fast = fast.next;
            }
        }
        return dummy.next;
    }
}

复杂度分析

  • 时间复杂度:O(n),其中 nn 是链表的长度。
  • 空间复杂度:O(1)。

方法三:递归

  • 对于头结点head来说,只要消除连续的重复节点,即可找到最终答案的头结点,也就是返回值。
  • 将返回值的下一个节点(如果有的话)作为参数进行递归。
  • 更新返回值的下一个节点。
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // base case
        if(head == null){
            return null;
        }
        // 找到链表头
        while(head != null && head.next != null && head.next.val == head.val){
            while(head.next != null && head.next.val == head.val){
                head = head.next;
            }
            head = head.next;
        }
        if(head == null){
            return head;
        }
        // 递归
        head.next = deleteDuplicates(head.next);
        return head;
    }
}