剑指offer-56、删除链表中重复的节点

题⽬描述

在⼀个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5

示例1 输⼊:{1,2,3,3,4,4,5} 返回值:{1,2,5}

思路及解答

hash统计

第一次遍历统计频率,第二次遍历删除重复节点

java 复制代码
import java.util.HashMap;

public class Solution {
    public ListNode deleteDuplication(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        
        // 第一次遍历:统计每个节点值出现的次数
        HashMap<Integer, Integer> countMap = new HashMap<>();
        ListNode current = head;
        while (current != null) {
            countMap.put(current.val, countMap.getOrDefault(current.val, 0) + 1);
            current = current.next;
        }
        
        // 第二次遍历:删除重复节点
        ListNode dummy = new ListNode(-1); // 哑节点简化边界处理
        dummy.next = head;
        ListNode prev = dummy;
        current = head;
        
        while (current != null) {
            if (countMap.get(current.val) > 1) {
                // 当前节点重复,跳过
                prev.next = current.next;
            } else {
                // 当前节点不重复,移动prev指针
                prev = prev.next;
            }
            current = current.next;
        }
        
        return dummy.next;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

直接遍历(推荐)

注意,题目已经提到是排序的节点,那么就可以直接原地删除

对⽐前后两个元素,如果相同的情况下,接着遍历后⾯的元素,直到元素不相等的时候,将前⾯的指针指向最后⼀个相同的元素的后⾯,相当于跳过了相同的元素。

java 复制代码
public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        //遍历链表,直接删除
        if(pHead == null || pHead.next == null) return pHead;
        ListNode head = new ListNode(0);
        head.next = pHead;
        ListNode cur = head.next;
        ListNode pre = head;
        while(cur != null){
            //将重复的结点都遍历过,然后将后面节点复制给pre结点后面
            if(cur.next != null && cur.val == cur.next.val){
                while(cur.next != null &&  cur.val == cur.next.val){
                    cur = cur.next;
                }
                pre.next = cur.next;
                cur = cur.next;
            }else{
                pre = pre.next;
                cur = cur.next;
            }
        }
        return head.next;
    }
}
  • 空间复杂度为 O(1) ,没有借助额外的空间
  • 时间复杂度为 O(n) ,只遍历了⼀次链表

递归

将大问题分解为当前节点+剩余链表的子问题

java 复制代码
/**
 * 递归法:分治思想解决子问题
 * 思路:将大问题分解为当前节点+剩余链表的子问题
 * 
 */
public class Solution {
    public ListNode deleteDuplication(ListNode head) {
        // 递归终止条件:空链表或单节点链表
        if (head == null || head.next == null) {
            return head;
        }
        
        // 情况1:当前节点与下一节点重复
        if (head.val == head.next.val) {
            // 跳过所有重复节点,找到第一个不重复的节点
            ListNode node = head.next;
            while (node != null && head.val == node.val) {
                node = node.next;
            }
            // 递归处理剩余部分
            return deleteDuplication(node);
        } 
        // 情况2:当前节点不重复
        else {
            head.next = deleteDuplication(head.next);
            return head;
        }
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n) ,递归栈空间

三指针法

使用pre、cur、next三个指针精确控制删除范围

java 复制代码
public class Solution {
    public ListNode deleteDuplication(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy;    // 前驱指针
        ListNode cur = head;     // 当前指针
        ListNode next = null;     // 后继指针
        
        while (cur != null && cur.next != null) {
            next = cur.next;
            
            // 发现重复节点
            if (cur.val == next.val) {
                // 移动next直到找到不重复的节点
                while (next != null && cur.val == next.val) {
                    next = next.next;
                }
                // 跳过所有重复节点
                pre.next = next;
                cur = next;
            } 
            // 没有重复,正常移动指针
            else {
                pre = cur;
                cur = cur.next;
            }
        }
        
        return dummy.next;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
相关推荐
Elastic 中国社区官方博客12 分钟前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn18825 分钟前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn1 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su1 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
我命由我123452 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
苜蓿muxu3 小时前
Java 字符串拼接:+、StringBuilder、StringBuffer的区别
java
摇滚侠3 小时前
《Java并发编程之美》第 4 章 Java 并发包中原子操作类原理剖析 阅读笔记 7
java·笔记·python
旋律4144 小时前
同义方法区别(增加类与删除类)
java·后端
学到老活到老4 小时前
方法执行完了,对象为什么还占着内存?
java·jvm