删除链表中重复的结点(手把手带你理解思路,从错误代码带你逐步完善代码,非常实用!)

简介: 删除链表中重复的结点(手把手带你理解思路,从错误代码带你逐步完善代码,非常实用!)

题目概述(简单难度)

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

示例1

输入:{1,2,3,3,4,4,5}
返回值:{1,2,5}


附上牛客网链接:

点击此处进入牛客网


思路与代码

思路展现

1:既然是删除链表中的所有重复节点,留下不重复的节点,那么就肯定要遍历链表中的所有节点了,则必须使用我们的老朋友cur变量来遍历链表了.

2:那么其余不重复的节点我们该如何存储呢?

可以建立一个新的链表来存储我们不重复的节点,最终返回我们新链表头节点的下一个节点即可,也就是我们第一个插入新链表的节点.

3:我们的思路是这样的:使用cur开始遍历我们的新的链表,同时比较我们的cur指针所指向的节点的值域与其下一个节点的的值域的大小,如果相等的话,cur就需要继续往下遍历,直至找到不相等的节点,存入我们的新的链表.

4:当然代码中包含了很多种特殊情况和细节点,在下面的代码解析中我会挨个来给大家分析,现在就先来看下我们的代码把.


代码示例

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead==null){
            return null;
        }
        //创建新链表的头节点
        ListNode newHead=new ListNode(-1);
         //cur指针指向原链表的头节点pHead
        ListNode cur=pHead;
        //tmp指针指向新链表的头节点newHead
        ListNode tmp=newHead;
        while(cur!=null){
            //要加cur.next!=null
            if((cur.next!=null)&&(cur.val==cur.next.val)){
                //要加cur.next!=null
                while(cur.next!=null&&cur.val==cur.next.val){
                    cur=cur.next;
                }
                //要多走一步
                cur=cur.next;
            }else{
                tmp.next=cur;
                tmp=tmp.next;
                cur=cur.next;
            }
        }
        //特殊情况
        tmp.next=null;
        return newHead.next;
    }
}

代码解析

正常情况

如下的代码也是我们会经常写的代码,也是不考虑特殊情况的代码,所以运行起来会报错,但是很多人却不知道哪里出了问题,下面我带大家来一一剖析:

先来看一组代码,这组代码可以对应如下的图一块看:

2.png


public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead==null){
            return null;
        }
        //创建新链表的头节点
        ListNode newHead=new ListNode(-1);
        //cur指针指向原链表的头节点pHead
        ListNode cur=pHead;
        //tmp指针指向新链表的头节点newHead
        ListNode tmp=newHead;
        while(cur!=null){
            if(cur.val==cur.next.val){
                while(cur.val==cur.next.val){
                    cur=cur.next;
                }
                //要多走一步,这里需要注意
                cur=cur.next;
            }else{
                tmp.next=cur;
                tmp=tmp.next;
                cur=cur.next;
            }
        }
        return newHead.next;
    }
}

对于图片中这种情况来说,这组代码看起来非常完美,我们先来分析下代码

1:首先判断链表是否为空,如果为空,则返回null

2:开始遍历我们的链表,然后开始判断cur指针所指向的节点的值域与其下一个节点的的值域是否相等,如果相等,cur就继续往下遍历,直至找到不相等的节点,但是这里也有需要注意的点:

就是为什么if中还要加一个while循环?

假如我们不加这个while循环,我们来看代码是怎么执行的,当cur这个指针变量走到了第三个15的时候,此时15不等于89,此时就会执行else中的语句,else中的语句是为了将不重复的节点插入到我们用于存储这些节点的新链表当中,此时就会把第三个值域为15的节点加入到我们存储不重复节点的新链表中,可以根据题意,值域为15的节点都应该算是重复节点,重复节点是应该被删掉的,怎么能存入到我们的新链表当中去呢?

所以为了防止这种情况的发生,我们在if中加入了while循环,用while循环代替if语句将重复的节点走过,当重复的这组节点的最后一个节点的值域与下一个节点的值域不相同时,就直接执行while循环外,if循环内的cur=cur.next这条语句,此时就直接跳到了下一个节点,完美避免了上述情况的发生.这个思路比较巧妙,希望大家好好思考.

3:else语句中执行的就是我们将不重复的节点插入新链表的语句,每插入一个节点,tmp和cur就继续往下遍历即可.

4:最终返回我们新链表的头节点的下一个节点,即我们插入新链表的第一个不重复的节点,返回它的原因是从这个节点以及它往后的节点组成的链表就是我们要的存储不重复节点的新链表。

经过我们的分析,感觉这个代码很完美,一运行,直接空指针异常,这是为什么?下面经过讨论几种特殊情况以及代码的完善,相信大家就会了解为什么代码运行不过了.


特殊情况1(完善第一步)

我们先来改变下原先链表节点的值域,改变后如下图所示:

2.png

这种情况的链表按照刚才的代码一定会发生空指针异常,这是为什么呢?

原因是经过上述代码执行后,我们的cur最终会来到91这个节点,此时回到while循环判断cur非空,非空后执行我们的if判断条件cur.val==cur.next.val,也就是这个判断条件发生了空指针异常,原因是cur为此时链表最后一个节点.cur.next=null.那么cur.next.val就会发生空指针异常,相当于null.val了.所以此处的if条件判断应该加上cur.next!=null这一条件,与之前的判断条件用逻辑与(&&)连接.完善后的代码如下:

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead==null){
            return null;
        }
        //创建新链表的头节点
        ListNode newHead=new ListNode(-1);
        //cur指针指向原链表的头节点pHead
        ListNode cur=pHead;
         //tmp指针指向新链表的头节点newHead
        ListNode tmp=newHead;
        while(cur!=null){
        //特殊情况1所加的判断条件cur.next!=null
            if(cur.next!=null&&cur.val==cur.next.val){
                while(cur.val==cur.next.val){
                    cur=cur.next;
                }
                cur=cur.next;
            }else{
                tmp.next=cur;
                tmp=tmp.next;
                cur=cur.next;
            }
        }
        return newHead.next;
    }
}

你以为这就完了?当然不,经过运行,卧槽?还报空指针异常,有些小伙伴就纳闷了,看来还是有特殊情况没有考虑不到,别慌,我们继续来完善.


特殊情况2(完善第二步)

下面我们再来看第二种特殊情况,还是修改下链表中的节点的值域,如下图所示:

2.png

与特殊情况1中不同的是,我们会发现除了首节点,我们的其他节点都是重复节点,所以最终我们不重复的节点只有首节点一个

执行完特殊情况2中完善的代码后我们会发现,最终cur会一直停留在if中的while循环内,那么cur此时会走到最后一个89,回到while循环中执行cur.val==cur.next.val这条语句,也就是这个while循环条件发生了空指针异常,原因是cur为此时链表最后一个节点.cur.next=null.那么cur.next.val就会发生空指针异常,相当于null.val了,所以同样我们也要为if内的while循环加上cur.next!=null这一条件,与之前的判断条件用逻辑与(&&)连接`.完善后的代码如下:

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead==null){
            return null;
        }
        //创建新链表的头节点
        ListNode newHead=new ListNode(-1);
        //cur指针指向原链表的头节点pHead
        ListNode cur=pHead;
         //tmp指针指向新链表的头节点newHead
        ListNode tmp=newHead;
        while(cur!=null){
            //特殊情况1所加的判断条件cur.next!=null
            if(cur.next!=null&&cur.val==cur.next.val){
                //特殊情况2所加的判断条件cur.next!=null
                while(cur.next!=null&&cur.val==cur.next.val){
                    cur=cur.next;
                }
                cur=cur.next;
            }else{
                tmp.next=cur;
                tmp=tmp.next;
                cur=cur.next;
            }
        }
        return newHead.next;
    }
}

完善过后总应该没问题了把,我们会发现报了如下错误:

2.png

这到底是个啥意思?来看下面的分析


特殊情况3(完善最终曲)

在特殊情况2中,有一个用例没有通过,原因是什么?

答案很简单,在特殊情况2中我们最后只有首节点插入到了新的链表中,如下图所示:

2.png

但是我们知道的是,我们首节点的next域并不是null,其保存着下一个节点的地址,所以这个首节点作为新链表的尾节点,其next域会因为不是null而报错,根据链表的标准,链表的尾节点,也就是链表的最后一个节点必须为null,这个链表才成立,所以在最后返回新链表的头节点前,还需要手动将新链表的尾节点的next域置空一次,不管它之前是否为null,都有必要执行一次tmp.next=null语句将next域置空.

再来看之前报错的截图:最终输出[2,4,5,5]的原因就是新链表中值域为4的这个节点作为链表的尾节点,其next域依然保存着原链表中值域为5的节点的地址,不为null,所以最终输出就成了[2,4,5,5],当我们最后再将新链表尾节点next域置空后,此时就成了[2,4]了.

最终代码正如之前给出的示例一样:

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead==null){
            return null;
        }
        //创建新链表的头节点
        ListNode newHead=new ListNode(-1);
        //cur指针指向原链表的头节点pHead
        ListNode cur=pHead;
         //tmp指针指向新链表的头节点newHead
        ListNode tmp=newHead;
        while(cur!=null){
            //特殊情况1所加的判断条件cur.next!=null
            if(cur.next!=null&&cur.val==cur.next.val){
                //特殊情况2所加的判断条件cur.next!=null
                while(cur.next!=null&&cur.val==cur.next.val){
                    cur=cur.next;
                }
                cur=cur.next;
            }else{
                tmp.next=cur;
                tmp=tmp.next;
                cur=cur.next;
            }
        }
        //特殊情况3所加代码
        tmp.next=null;
        return newHead.next;
    }
}

总结

这个题目需要考虑的特殊情况非常之多,需要小伙伴们下来好好研究下这个题目,只有将所有情况考虑进去,我们的代码才不会出错.


相关文章
|
2月前
|
算法
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
69 1
|
1月前
|
存储 算法 搜索推荐
链表的中间结点
【10月更文挑战第24天】链表的中间结点是链表操作中的一个重要概念,通过快慢指针法等方法可以高效地找到它。中间结点在数据分割、平衡检测、算法应用等方面都有着重要的意义。在实际编程中,理解和掌握寻找中间结点的方法对于解决链表相关问题具有重要价值。
15 1
|
3月前
链表的中间结点
链表的中间结点
181 57
|
6月前
|
算法
数据结构和算法学习记录——线性表之双向链表(上)-结点类型定义、初始化函数、创建新结点函数、尾插函数、打印函数、尾删函数
数据结构和算法学习记录——线性表之双向链表(上)-结点类型定义、初始化函数、创建新结点函数、尾插函数、打印函数、尾删函数
52 0
|
2月前
【LeetCode 09】19 删除链表的倒数第 N 个结点
【LeetCode 09】19 删除链表的倒数第 N 个结点
17 0
|
3月前
|
存储 算法 C语言
C语言手撕实战代码_循环单链表和循环双链表
本文档详细介绍了用C语言实现循环单链表和循环双链表的相关算法。包括循环单链表的建立、逆转、左移、拆分及合并等操作;以及双链表的建立、遍历、排序和循环双链表的重组。通过具体示例和代码片段,展示了每种算法的实现思路与步骤,帮助读者深入理解并掌握这些数据结构的基本操作方法。
|
4月前
|
算法
LeetCode第19题删除链表的倒数第 N 个结点
该文章介绍了 LeetCode 第 19 题删除链表的倒数第 N 个结点的解法,通过使用快慢双指针,先将快指针移动 n 步,然后快慢指针一起遍历,直到快指针到达链尾,从而找到倒数第 N 个结点的前一个结点进行删除,同时总结了快慢指针可减少链表遍历次数的特点。
LeetCode第19题删除链表的倒数第 N 个结点
|
5月前
【数据结构OJ题】链表中倒数第k个结点
牛客题目——链表中倒数第k个结点
39 1
【数据结构OJ题】链表中倒数第k个结点
|
4月前
【刷题记录】链表的中间结点
【刷题记录】链表的中间结点
|
5月前
【数据结构OJ题】链表的中间结点
力扣题目——链表的中间结点
28 0
【数据结构OJ题】链表的中间结点