前言

  最近在刷《剑指offer》的题,其中有一道题目叫做删除链表中重复的节点,我想了半天没想到比较好的解决办法,于是看了看大佬的解析(菜哭了)。不看不知道,一看吓一跳,这尼玛写的也太妙了,忍不住写篇博客记录一下这个解题思路和代码。


题目描述

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


解题思路

  这道题我们分两种情况来考虑:

  1. 首先第一种情况:头节点的值存在重复;比如1->1->1->2->3->3->4,前面这个链表的头节点重复了3次,所以这时候,我们应该舍弃前3个重复的节点1,将2作为新的头节点,再继续向后判断;
  2. 第二种情况就是头节点并不与它的下一个节点重复;比如上面的这个链表,我们去除了前面的3个1之后,剩下2->3->3->4,这时候,头节点不与后面的节点重复了,那我们保留头节点,并继续向后判断,发现后面后面的两个3发生了重复,于是,我们去除这个两个节点,并让原来头节点的next指向去除重复后的下一个位置,也就变成了2->4;若4后面还有其他重复,则我们去除重复后,让4指向剩下的部分;

代码实现

  其实上面的思路并不是很难想到,关键是代码如何实现呢?下面这个代码就是大佬对于上面这个思路的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public ListNode deleteDuplication(ListNode pHead) {
// 若头节点为空,或者链表只有一个节点,则必没有重复,值将返回
if(pHead == null || pHead.next == null) {
return pHead;
}

// 保存头节点的下一个节点,上面已经判断了pHead.next不是空
ListNode next = pHead.next;
// 若头节点的值与下一个节点的值相同
if(pHead.val == next.val) {
do{
// 则继续向前找出与头节点重复的节点
// 直到找到第一个与头节点不同的节点后,退出循环
next = next.next;
}while(next != null && next.val == pHead.val);

// 舍弃前面的所有重复节点,将当前第一个与头节点不同的节点作为头节点,递归调用原方法,并直接返回
return deleteDuplication(next);
}else {
// 若头节点与它的下一个节点值不同,则将头节点的下一个节点作为头节点,递归调用方法
// 并将返回值赋给头节点的next属性
pHead.next = deleteDuplication(next);
return pHead;
}
}
1
2
3
4
5
6
7
8
9
// 以下是节点ListNode
class ListNode {
int val;
ListNode next = null;

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

  上面的代码我加了点注释,看得难受可以复制到编辑器中,删掉注释再看。

  上面这段代码,给我的感觉就是把递归用的出神入化(可能是我太菜了)。除去注释,短短几行代码,就将上面的思路完全实现,下面我来解读一下:

  上面的代码首先做了特判,若传入的头节点是空,或者没有后续节点,那就不用去重,直接返回。这之后,先将头节点的下一个节点保存。

  我们先判断当前是否满足前面说的第一种情况:头节点发生了重复,若发生了这种情况,就一直向后找,直到找到第一个不与头节点重复的节点,然后我们舍弃前面的节点,把这个节点当作头节点,递归调用方法,并直接将返回值返回,这相当于是把后面剩下的部分当作一条新的链表,而前面重复的就直接舍弃了;

  若当前链表是我们之前说的第二种情况:头节点不重复,则我们将头节点的下一个节点作为参数,递归调用原方法,将除去头节点后的子链表看作是一个新链表,而方法返回值就是这个子链表去重后的链表,我们将其与原来头节点关联,就完整地去重了。

  上面代码最精妙的地方就是递归,将原链表中除去头节点的剩余部分,当作一个新链表进行处理,短短几行代码,就实现了去重。