题目描述
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中没有重复出现的数字。
示例1:
输入: head = [1, 2, 3, 3, 4, 4, 5]
输出: head = [1, 2, 5]
示例2:
输入: head = [1, 1, 1, 2, 3]
输出: head = [2, 3]
题目分析
对于此题的简单版本,参见算法修炼之路——【链表】Leetcode83 删除重复节点;
我们直接考虑,这里简单来看需要找到重复数字的起始位置,直接是两个问题:
- 重复数字的判断问题
- 重复数字的定界问题
对于问题1,我们可以根据一个指针和此指针之后的节点值来判断;
对于指针,当当前数值与后一节点的数值不同时,停止遍历,此时需要联合问题2。
问题2,当我们检测到重复数字后,需要判断这个边界,即找到最后一个重复数字。此时可以将问题2看作问题1的衍生问题,这时我们需要第二个指针,此指针为临时指针,用来确定结束边界,但是停止位置为重复数字的最后一个节点,见图1:
图1
最简单的情况如图1所示,定界节点均在链表内部(无边界问题),此时直接将currP.next
指向tmpP.next
即可,这里需要特殊考虑两种情况:
- 当
head
值即为重复数值时,图1中的currP
则会打破我们的设定(currP
在重复数字的前一个节点),这个时候需要借助哨兵机制,进行统一规则:
- 当重复数字在链表尾部出现时,来检验我们的统一规则是否适用:
此时可以看到,当链表末尾出现重复数字后,我们假设的规则依然适用,则截止到这里,我们的想法已经在通常情况、首部边界情况和尾部边界情况均进行了测试与适配,剩下就是进行编码调试。
步骤罗列
- 初始化我们需要的指针和哨兵:
dummyHead = new ListNode(-1); currP = dummyHead
; - 判断
currP.next
的数值和currP.next.next
是否为空,和数值是否相等;直至这些条件同时不满足,令currP.next = tmpP.next
;- 检测 重复数字
- 寻找最后一个重复数字
- 删除重复部分
- 返回
dummyHead.next
;
解题代码
public static ListNode solutionWithTwoP(ListNode head){
if(head == null || head.next == null) return head;
//1. init pointers and dummyHead
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode currP = dummyHead;
while(currP.next != null && currP.next.next != null){
//1. detected repeat node
if(currP.next.val == currP.next.next.val){
ListNode tmpP = currP.next;
//2. found the last repeated node
while(tmpP != null && tmpP.next != null
&& tmpP.val == tmpP.next.val)
tmpP = tmpP.next;
//3. delete repeated node
currP.next = tmpP.next;
}else{
currP = currP.next;
}
}
return dummyHead.next;
}
复杂度分析
时间复杂度:我们对链表仅进行了一次遍历,故时间复杂度为0(n);
空间复杂度:没有辅助容器,故为O(1);
GitHub代码
完整可运行代码参见GitHub。