啃算法:05 链表数据去重和删除所以重复的数据

127 阅读5分钟

链表数据重复去重问题,牛群问题

链表去重排序

农场里有一群牛,每头牛都有一个独特的编号,编号由一个整数表示,整数范围是[0, 200]。牛群中的牛用单链表表示,链表已经按照非降序排列。 因为一些事故,导致一头牛可能多次出现在链表中。给你一个链表的头 head,删除链表中所有重复的编号,只留下所有牛的不重复编号。返回已排序的链表。 示例1 输入: {1,2,2,3,3,4,5,5} 复制 返回值: {1,2,3,4,5}

这里我们自定义了一个链表对象。

    static public class ListNode {
        int val;
        ListNode next = null;

        public ListNode(int val) {
            this.val = val;
        }
    }
}

添加数据

 int [] arrays={1,2,2,3,3,4,5,5};
        ListNode head=new ListNode(arrays[0]);
        ListNode next=head;
        for (int i=1;i<arrays.length;i++){
            ListNode node=new ListNode(arrays[i]);
            next.next=node;
            next=node;
        }

OK,链表数据是添加成功了。那么我们如何遍历这个链表呢?可以看到,上面的代码里面我们最后一个节点的next 一定是空的。所以我们这里通过while循环, 判断当前对象的next是否为空,为空了就表示是链表的最后一个节点了。

        ListNode showParent=head;
        while (showParent!=null){
            System.out.println(showParent.val);
            showParent=showParent.next;
        }

有序链表去重问题

我们假设链表数据本身就是已经排序好了的数据。例如:

{1,2,2,3,3,4,5,5}

所以我们的判断思路是非常简单的。

  • 循环到1,我们判断1的var 是否和2 的var 相同。这明显不相同,我们将 1的next 赋值给循环对象,进入到2的循环。
  • 循环到第一个2,我们判断第一个2的var 是否和第2个2的var 是否相同,明显是相同的,那么我们就将第一个2的next 换成第2个2的next 也就是3,进入下一次循环。
  • 还是循环到第一个2,现在第一个2的var 还是2,因为上一次循环把第一个2的next 换成了3,所以这次不相同,进入3的循环。

以此类推。

    static public ListNode deleteDuplicates(ListNode head) {
        ListNode cur = head;
        while (cur != null && cur.next != null) {
            if (cur.val == cur.next.val) {
                cur.next = cur.next.next;
            } else {
                cur = cur.next;
            }
        }
        return head;
    }

可以看到,时间复杂度是O(n),空间复杂度是O(1)。

有序列表中删除重复元素

我们假设链表数据本身就是已经排序好了的数据。例如:

{1,2,2,3,3,4,5,5}

但是我们想要的是 {1,4} 。2,3,5 出现了重复数据,可能数据本身就不安全了。

要求:空间复杂度 O(n),时间复杂度 O(n)

进阶:空间复杂度 O(1),时间复杂度 O(n)

这里又一个概念很重要,那就是我们需要把没有重复的节点拼接成一个链表。

双重循环

我们假设一种概念,比如说 数据是:{1,1,2,2,3,3,4,5,5} 结合我们的需求,我们只能输出4。那么我们如何拿到头节点呢?为了保证我们新链表有一个永远不重复的头结点,同时便于计算, 我们可以向链表头部添加一个新的节点,比如说-1,那么这个时候的数据便成了 {-1,1,1,2,2,3,3,4,5,5} ,那么我们新链表的头结点就是我们新增的这个-1了。

  • 遍历链表,每次比较相邻两个节点,如果遇到两个相邻的节点相同,则开新的内循环将这一段所有的相同都遍历过去。
  • 在外层循环中链接跳出内循环的节点。
  • 返回的时候去掉添加的表头。
    static public ListNode deleteDuplicates(ListNode head) {
        ListNode res=new ListNode(-1);
        res.next=head;
        ListNode cur=res;
        while (cur.next!=null&&cur.next.next!=null) {
            if (cur.next.val == cur.next.next.val) {
                int temp=cur.next.val;
                while (cur.next!=null&& cur.next.val==temp){
                    // 
                    cur.next=cur.next.next;
                }
            } else {
                cur = cur.next;
            }
        }
        return res.next;
    }

整体来说,每个节点只会遍历一次,所以时间复杂度是O(n),空间复杂度是O(1)。

hash表计数模式

hash表是一种根据关键key 直接访问值的一种数据结构。故可以通过hash表来统计频率、快速检验某个元素是否出现过等。

  • 遍历一次链表,记录每个节点出现的次数。
  • 添加一个不重复的表头。
  • 再次遍历链表,判断链表中的次数,只有为1的留下,其他的都舍弃。
  • 返回的时候,去掉添加到表头。
    static public ListNode deleteDuplicates(ListNode head) {
        Map<Integer, Integer> map = new HashMap<>();
        ListNode cur = head;
        while (cur != null) {
            if (map.containsKey(cur.val)) {
                map.put(cur.val, map.get(cur.val) + 1);
            } else {
                map.put(cur.val, 1);
            }
            cur = cur.next;
        }
        ListNode res = new ListNode(-1);
        res.next = head;
        cur = res;
        while (cur!=null){
            if (cur.next!=null){
                if (map.get(cur.next.val)!=1){
                    cur.next=cur.next.next;
                }else {
                    cur=cur.next;
                }
            }else {
                cur= null;
            }

        }
        return res.next;
    }

时间复杂度是O(n),因为两次遍历。hash表每次计数,每次查询都是O(1)。 空间复杂度是O(n),因为基于hashMap 开辟了空间,最多是n.

通过双指针变量计数

思路还是:

  • 先定义一个绝对不重复的头结点。
  • 对真正的头结点和头结点的下一个节点的值进行对比,如果是相同的,统计数就加1,同时将循环节点的next节点换成next的next节点,重新进入循环,如果说不相同且统计数是0,那么就把当前节点设置到自定义节点的next里面,如果说大于0,就设置循环节点的next。
  • 最终返回的时候,移除自定义的头结点。

实现代码:

static public ListNode deleteDuplicates(ListNode head) {
    ListNode res = new ListNode(-1);
    res.next = head;
    ListNode cur = res;
    ListNode pre = res;
    int count=0;
    while (cur!=null&&cur.next!=null){
        if (cur.val==cur.next.val){
            count++;
            // 跳过next 节点。所以比对的还是当前的cur 和他的下一个节点。
            cur.next=cur.next.next;
        }else {
            if (count>0){
                // 因为我们删除所有重复的,所以赋值 cur.next.
                pre.next=cur.next;
                count=0;
            }else {
                pre=cur;
            }
            cur=cur.next;
        }
    }
    if (count>0){
        // 设置最后的节点为null。如果count 等于0,那么因为最后的节点的next 就是空就不需要设置。
        pre.next= null;
    }
    return res.next;
}