力扣82删除排序链表中的重复元素 II:思路分析+代码实现+方法总结(三指针法&快慢指针法【双指针】&递归法)

简介: 力扣82删除排序链表中的重复元素 II:思路分析+代码实现+方法总结(三指针法&快慢指针法【双指针】&递归法)

第一部分:题目描述

🏠 链接:82. 删除排序链表中的重复元素 II - 力扣(LeetCode)

⭐ 难度:中等

第二部分:代码实现

2.1 三指针法

p1 是待删除的上一个节点,每次循环对比 p2、p3 的值。

  • 如果 p2 与 p3 的值重复,那么 p3 继续后移,直到找到与 p2 不重复的节点,p1 指向 p3 完成删除。
  • 如果 p2 与 p3 的值不重复,p1,p2,p3 向后平移一位,继续上面的操作。
  • p2 或 p3 为 null 退出循环。
  • p2 为 null 的情况,比如链表为 1 1 1 null。
p1 p2 p3
s, 1, 1, 1, 2, 3, null
p1 p2    p3
s, 1, 1, 1, 2, 3, null
p1 p2       p3
s, 1, 1, 1, 2, 3, null
p1 p3
s, 2, 3, null
p1 p2 p3
s, 2, 3, null
   p1 p2 p3
s, 2, 3, null

代码:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // 当链表无节点或者只存在头节点,直接返回 null / 头节点
        if (head == null || head.next == null) {
            return head;
        }
        // 设置一个哨兵节点(伪头节点)
        ListNode sentinel = new ListNode(-999, head);
        // p1 指向哨兵节点
        ListNode p1 = sentinel;
        ListNode p2, p3;
        // 设置 p2 为 p1 的下一个节点,p3 为 p1 的下下个节点
        // 如果 此时 p2 或 p3 为空节点说明已遍历完链表,再无重复节点
        while ((p2 = p1.next) != null
                && (p3 = p2.next) != null) {
            // 如果 p2 与 它的下一个节点val值重复,则需要删除当前p2、p3指向的节点以及后面与p2.val相同值的节点
            if (p2.val == p3.val) {
                // 使用 p3 继续向后遍历,直到找到与p2.val不相同值的节点
                while ((p3 = p3.next) != null && p3.val == p2.val) {
                }
                // 此时 p3 指向的节点为不想同值的节点
                // 为了跳过当前 p2 到 p3以前 的节点,可以将p2的上一个节点p1的next 指向p3
                p1.next = p3;
                // 继续下一次循环即可,在循环条件中再次对p2、p3赋值使得p2、p3分别指向p1的下个节点与下下个节点
            } else {
                // 如果 p2 与 它的下一个节点p3的val值不重复,说明链表中不存在与 p2.val 相等的值的节点,
                // 则只需要将 p1、p2、p3后挪一位即可,由于在外层while循环中会对p2、p3赋值,因此在这里只需要对p1后移即可
                p1 = p1.next;
            }
        }
        // 返回真正的头节点
        return sentinel.next;
    }
}

2.2 快慢指针法(双指针)

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // 如果链表无节点或者只有一个节点,则返回 null / head
        if (head == null || head.next == null) {
            return head;
        }
        // 设置一个哨兵 sentinel,它的下一个节点为链表头节点 head
        ListNode sentinel = new ListNode(-999, head);
        // 快指针,指向 head
        ListNode fast = head;
        // 慢指针,指向 哨兵
        ListNode slow = sentinel;
        // 记录某个值出现次数
        int count = 0;
        // 使用快指针进行遍历链表
        while (fast != null) {
            // 如果快指针指向的val与慢指针下一个节点的val相等
            if (fast.val == slow.next.val) {
                // 则fast继续向后遍历
                fast = fast.next;
                // 值出现次数 +1
                count++;
            } else {
                // 如果fast遇到与慢指针下一个节点的val不重复的值
                // 判断 慢指针下一个节点的val出现次数即 count 是否为1
                if (count == 1) {
                    // 如果只出现了一次,说明未出现重复的值,则慢指针向后移动一位即可
                    // 此时fast的位置一定是在 slow 的后面一位
                    slow = slow.next;
                } else {
                    // 如果出现了多次,则需要忽略所有与 slow.next.val 具有相等值的节点
                    // 如何忽略呢?只需要更新slow的下一个节点为fast即可
                    slow.next = fast;
                }
                // 遇到了不重复的值,做完了对上一个count的处理后,重新清0进行下一次计算
                count = 0;
            }
        }
        // 当 fast 为空时会退出循环,但并未对count进行后续处理,因此这里还需要处理一下
        // 如果不为1,说明出现了重复节点,如何忽略这些重复节点呢?只需要更新slow的下一个节点为null即可
        if (count != 1) {
            slow.next = null;
        }
        // 返回真正的头节点
        return sentinel.next;
    }
}

2.3 递归

递归函数负责返回:从当前节点(我)开始,完成去重的链表。

  1. 若我与 next 重复,一直找到下一个不重复的节点,以它的返回结果为准。
  2. 若我与 next 不重复,返回我,同时更新 next。
// 链表:1 -> 1 -> 1 -> 2 -> 3
deleteDuplicates(ListNode p = 1) {
    // 找下个不重复的
  deleteDuplicates(ListNode p = 1) {
        deleteDuplicates(ListNode p = 1) {
      deleteDuplicates(ListNode p = 2) {
                2.next=deleteDuplicates(ListNode p = 3) {
          // 只剩一个节点,返回
                    return 3
                }
                return 2
      }
        }
    }
}

代码:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // 边界节点的考虑
        if (head == null || head.next == null) {
            return head;
        }
        // 如果节点的val重复,则删除所有为val的节点
        if (head.val == head.next.val) {
            // 找到下下个节点
            ListNode tmp = head.next.next;
            // 循环遍历后面节点直到找到一个与当前节点val不同的节点
            while (tmp != null && tmp.val == head.val) {
                tmp = tmp.next;
            }
            // 找到的tmp就是与 head 取值不相同的节点
            // 直接舍弃从head开始的这一部分节点,
            // 从 tmp 开始继续递归调用,return返回的节点一般是作为某个节点的下一个节点以达到舍弃的效果
            return deleteDuplicates(tmp);
        }
        // 如果不重复,就更新它的next节点,实际是对后续的节点进行去重操作
        head.next = deleteDuplicates(head.next);
        // 更新next后,返回当前这个不重复的节点即可
        return head;
    }
}


相关文章
|
算法 容器
【算法】——双指针算法合集(力扣)
移动零,复写零,快乐数,盛最多水的容器,有效三角形的个数,和为s的两个数(查找总价格为目标值的两个商品 ),三数之和,四数之和
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
178 1
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
160 4
|
存储 Java
HashMap之链表转红黑树(树化 )-treefyBin方法源码解读(所有涉及到的方法均有详细解读,欢迎指正)
本文详细解析了Java HashMap中链表转红黑树的机制,包括树化条件(链表长度达8且数组长度≥64)及转换流程,确保高效处理大量数据。
856 1
|
存储 算法
LeetCode第83题删除排序链表中的重复元素
文章介绍了LeetCode第83题"删除排序链表中的重复元素"的解法,使用双指针技术在原链表上原地删除重复元素,提供了一种时间和空间效率都较高的解决方案。
LeetCode第83题删除排序链表中的重复元素
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
206 0
Leetcode第三十三题(搜索旋转排序数组)
|
算法 索引
LeetCode第34题在排序数组中查找元素的第一个和最后一个位置
这篇文章介绍了LeetCode第34题"在排序数组中查找元素的第一个和最后一个位置"的解题方法,通过使用双指针法从数组两端向中间同时查找目标值,有效地找到了目标值的首次和最后一次出现的索引位置。
LeetCode第34题在排序数组中查找元素的第一个和最后一个位置
|
存储 算法 Java
LeetCode初级算法题:反转链表+统计N以内的素数+删除排序数组中的重复项Java详解
LeetCode初级算法题:反转链表+统计N以内的素数+删除排序数组中的重复项Java详解
209 0
|
算法 索引 Python
【Leetcode刷题Python】34. 在排序数组中查找元素的第一个和最后一个位置(二分查找)
解决LeetCode "在排序数组中查找元素的第一个和最后一个位置" 问题的方法。第一种方法是使用两次二分查找,首先找到目标值的最左边界,然后找到最右边界。第二种方法是利用Python的list.index()方法,先正序找到起始位置,再逆序找到结束位置,并给出了两种方法的Python实现代码。
241 0