本文正在参加「Python主题月」,详情查看 活动连接markdown
这是「牛客网」上的「JZ 56 删除链表中重复的结点」,难度为「较难」 。函数
Tag : 「剑指 Offer」、「链表」、「单链表」post
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。ui
例如,链表 1->2->3->3->4->4->5
处理后为 1->2->5
spa
示例 1:设计
输入:{1,2,3,3,4,4,5}
返回值:{1,2,5}
复制代码
要求:指针
时间:1 scode
空间:64 Morm
首先一个比较「直观且通用」的思路是,采用「边遍历边构造」的方式:排序
建一个「虚拟头节点」dummy
以减小边界判断,日后的答案链表会接在 dummy
后面;
使用 tail
表明当前有效链表的结尾;
经过原输入的 pHead
指针进行链表扫描。
对原链表进行遍历,只要原链表还没有到达结尾,咱们就重复以下决策(保留/跳过逻辑):
保留:pHead
已经没有下一个节点,pHead
能够被保留(插入到答案结尾指针 tail
后面);pHead
有一下个节点,可是值与 pHead
不相同,pHead
能够被保留;
跳过:当发现 pHead
与下一个节点值相同,须要对「连续相同一段」进行跳过。
举个 🌰,以题目示例 [1,2,3,3,4,4,5]
为例,使用图解的方式来感觉一下。
Java 代码:
class Solution {
public ListNode deleteDuplication(ListNode pHead) {
ListNode dummy = new ListNode(-1);
ListNode tail = dummy;
while (pHead != null) {
// 进入循环时,确保了 pHead 不会与上一节点相同
if (pHead.next == null || pHead.next.val != pHead.val) {
tail.next = pHead;
tail = pHead;
}
// 若是 pHead 与下一节点相同,跳过相同节点(到达「连续相同一段」的最后一位)
while (pHead.next != null && pHead.val == pHead.next.val) pHead = pHead.next;
pHead = pHead.next;
}
tail.next = null;
return dummy.next;
}
}
复制代码
Python 3 代码:
class Solution:
def deleteDuplication(self, pHead):
dummy = ListNode(-1)
tail = dummy
while pHead is not None:
# 进入循环时,确保了 pHead 不会与上一节点相同
if pHead.next is None or pHead.next.val != pHead.val:
tail.next = pHead
tail = pHead
# 若是 pHead 与下一节点相同,跳过相同节点(到达「连续相同一段」的最后一位)
while pHead.next is not None and pHead.val == pHead.next.val:
pHead = pHead.next
pHead = pHead.next
tail.next = None
return dummy.next
复制代码
递归解法相比于迭代解法,代码要简洁一些,但思惟难度要高一些。
首先不管是否为“链表”类的题目,在实现递归前,都须要先明确「咱们指望递归函数完成什么功能」,即设计好咱们的递归函数签名。
显然,咱们但愿存在一个递归函数:传入链表头结点,对传入链表的重复元素进行删除,返回操做后的链表头结点。
该功能与题目要咱们实现的 deleteDuplication
函数相同,所以咱们直接使用原函数做为递归函数便可。
以后再考虑「递归出口」和「递归环节的最小操做」:
递归出口:考虑什么状况下,咱们再也不须要「删除」操做。显然当传入的参数 pHead
为空,或者 pHead.next
为空时,必然不存在重复元素,可直接返回 pHead
;
递归环节的最小操做:以后再考虑删除逻辑该如何进行:
显然,当 pHead.val != pHead.next.val
时,pHead
是能够被保留的,所以咱们只须要将 pHead.next
传入递归函数,并将返回值做为 pHead.next
,而后返回 pHead
便可;
当 pHead.val == pHead.next.val
时,pHead
不能被保留,咱们须要使用临时变量 tmp
跳过「与 pHead.val
值相同的连续一段」,将 tmp
传入递归函数所得的结果做为本次返回。
Java 代码:
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
// 递归出口:当「输入节点为空」或者「不存在下一节点」,直接返回
if (pHead == null || pHead.next == null) return pHead;
if (pHead.val != pHead.next.val) {
// 若「当前节点」与「下一节点」值不一样,则当前节点能够被保留
pHead.next = deleteDuplication(pHead.next);
return pHead;
} else {
// 若「当前节点」与「下一节点」相同,须要跳过「值相同的连续一段」
ListNode tmp = pHead;
while (tmp != null && tmp.val == pHead.val) tmp = tmp.next;
return deleteDuplication(tmp);
}
}
}
复制代码
Python 3 代码:
class Solution:
def deleteDuplication(self, pHead):
# 递归出口:当「输入节点为空」或者「不存在下一节点」,直接返回
if pHead is None or pHead.next is None:
return pHead
if pHead.val != pHead.next.val:
# 若「当前节点」与「下一节点」值不一样,则当前节点能够被保留
pHead.next = self.deleteDuplication(pHead.next)
return pHead
else:
# 若「当前节点」与「下一节点」相同,须要跳过「值相同的连续一段」
tmp = pHead
while tmp is not None and tmp.val == pHead.val:
tmp = tmp.next
return self.deleteDuplication(tmp)
复制代码
本质没有改变,只须要抓住「遍历过程当中,节点什么时候可以被保留」便可。
Java 代码:
class Solution {
public ListNode deleteDuplication(ListNode head) {
if (head == null) return head;
ListNode dummy = new ListNode(-109);
ListNode tail = dummy;
while (head != null) {
// 值不相等才追加,确保了相同的节点只有第一个会被添加到答案
if (tail.val != head.val) {
tail.next = head;
tail = tail.next;
}
head = head.next;
}
tail.next = null;
return dummy.next;
}
}
复制代码
Python 3 代码:
class Solution:
def deleteDuplication(self, pHead):
if pHead is None:
return pHead
dummy = ListNode(-109)
tail = dummy
while pHead is not None:
# 值不相等才追加,确保了相同的节点只有第一个会被添加到答案
if tail.val != pHead.val:
tail.next = pHead
tail = tail.next
pHead = pHead.next
tail.next = None
return dummy.next
复制代码
时间复杂度:
空间复杂度:
这是咱们「剑指 の 精选」系列文章的第 No.56
篇,系列开始于 2021/07/01。
该系列会将「剑指 Offer」中比较经典而又不过期的题目都讲一遍。
在提供追求「证实」&「思路」的同时,提供最为简洁的代码。
欢迎关注,交个朋友 (`・ω・´)