[链表]82.83. 删除排序链表中的重复元素 I II(三指针法)
82. 删除排序链表中的重复元素 II(删除所有重复元素)
分类:链表、三指针法
思路:三指针法
算法设计:
设置一个pre指向head的前一个节点,last作为不重复链表的最后一个节点,head作为工作指针,dummy作为虚拟头结点。
把不重复链表设为新链表,和删除排序数组重复项的解题思路类似,last用于指示构造的新链表的末尾,每有一个满足条件的节点,就加入到last.next上。
初始时:
dummy->1->2->3->3->4->4->5
↑ ↑ ↑
last pre head
记录pre.val为backup,然后,比较当前head.val和backup:
- 如果head.val != backup,说明head和pre是不同数字,pre可以加入新链表,令last.next = pre,更新last = last.next,pre和head同步后移一位(pre = head、head = head.next),更新backup = pre.val;
- 如果head.val == backup,说明head和pre是同个数字,pre不能加入新链表,last保持不动,head不断后移(用while循环实现),直到head.val != backup,或head == null,更新pre和head(pre = head、head = head.next),更新backup = pre.val。(关键点,想到这一点问题基本解决了)
重复上述步骤,直到pre==null。(注意:加入新链表的是pre)
算法流程:
举例:
dummy->1->2->3->3->3->4->4->5
↑ ↑ ↑
last pre head
backup = pre.val = 1,head.val != backup,所以将pre加入last(last.next=pre),更新last = last.next,然后pre、head同步后移,更新backup为新pre.val.
dummy->1->2->3->3->3->4->4->5
↑ ↑ ↑
last pre head
backup = 2,head.val != backup,所以将pre加入last(last.next=pre),更新last = last.next,pre、head同步后移,更新backup。
dummy->1->2->3->3->3->4->4->5
↑ ↑ ↑
last pre head
backup = 3,head.val == backup,所以last保持不动,head进入while循环不断取head.next,直到head.val != backup,更新pre = head,head = head.next,更新backup = pre.val:
dummy->1->2->3->3->3->4->5
↑ ↑ ↑
last pre head
backup = 4,head.val != backup ,所以将pre加入last(last.next=pre),更新last = last.next,pre、head同步后移,更新backup = pre.val = 5:
dummy->1->2->3->3->3->4->5->null
↑ ↑ ↑
last pre head
可以发现此时head=null,但pre还未加入新链表中,所以head == null不能作为退出循环的条件。此时head==null,所以无法取到head.val,但此时的pre可以直接加入新链表:last.next = pre,然后更新pre = head = null,至此退出循环。(关键点1:边界条件的考虑)
最后将pre = null添加到last.next上,作为新链表的结尾。(关键点2:pre=null时的收尾处理)
实现时遇到的问题:
1、边界条件的考虑
通过上面的举例分析,可以发现当head=null时,pre还未加入新链表中,所以head == null不能作为退出循环的条件。又因为此时head==null,所以无法取到head.val,但此时的pre不可能和后面的其他节点重复,所以可以直接加入新链表:last.next = pre,然后更新pre = head = null,至此退出循环。
2、pre=null时的收尾处理(易遗漏)
例如:用例[1,1],直到pre == null退出循环时last也没有新元素加入,这样一来last.next仍然是初始状态,指向原链表,所以在退出循环后增加一步:
last.next = pre
使 last.next = null, 最终返回[]。
实现代码:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//特殊用例
if(head == null || head.next == null) return head;
ListNode dummy = new ListNode(0);//虚拟头结点
dummy.next = head;
//last指向新链表的末尾,pre指向head的前一个节点
ListNode last = dummy, pre = head;
head = head.next;
//记录下pre的val与head.val比较
int backup = pre.val;
while(pre != null){
//当head.val!=backup或head==null时
if(head == null || head.val != backup){
last.next = pre;
last = last.next;
}
//当head.val==backup时
else{
//head不断取head.next,直到head.val!=backup
while(head != null && head.val == backup){
head = head.next;
}
}
//更新pre,head
pre = head;
if(head != null) head = head.next;
//更新backup
if(pre != null) backup = pre.val;
}
last.next = pre;//易忽略点:见实现遇到的问题
return dummy.next;
}
}
83. 删除排序链表中的重复元素 I(重复元素保留一个)
分类:链表、三指针法
题目分析
和82题相比,本题更简单,对重复的元素只保留一个,但思路类似,仍然使用三指针法,但不需要使用变量backup记录pre.val,主要工作内容是:
pre指向head的前一个节点,两个指针始终同步移动,当head.val!=pre.val,说明head,pre是值不同的节点,将pre加入新链表。无论head.val和pre.val的大小关系如何,都将pre和head同步向后移动,直到pre == null退出。
最后也不需要额外将pre == null加入新链表末尾(举例分析可知,加入也不影响)。
思路:三指针法
设dummy为虚拟头结点,last指示新链表的最后一个节点,head作为工作节点,pre执行head的前一个节点。
初始时:新链表为空,所以last指向dummy
dummy->1->1->1->2->2->3
↑ ↑ ↑
last pre head
- 如果pre.val == head.val,说明head和pre是重复元素,pre,head继续不断同步向后移动,直到head.val != pre.val或head == null;
- 如果pre.val != head.val,说明head和pre是不同元素,将pre加入新链表(last.next = pre),同步向后移动pre和head(pre = head,head = head.next)。
以此类推,注意当head == null时,pre还未加入新链表,所以pre加入新链表,再更新pre = pre.next == null,退出while循环,
如下:
dummy->1->1->1->2->2->3->null
↑ ↑ ↑
last pre head
实现代码:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//特殊用例
if(head == null || head.next == null) return head;
//虚拟头结点
ListNode dummy = new ListNode(0);
dummy.next = head;
//设置各指针的初始状态
ListNode last = dummy, pre = head;
head = head.next;
while(pre != null){
if(head == null || pre.val != head.val){
last.next = pre;
last = last.next;
}
pre = head;
if(head != null) head = head.next;
}
return dummy.next;
}
}