53. 链表中环的入口结点
题目描述
知识点:链表
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路
链表结构如下图所示,图片来自牛客网评论。
思路一:断链法
设定一前一后两个指针,以相同的速度后移。每次前面指针经过的结点都要和之后的链表结点断开,这样一来,两指针第一次经过环的入口结点之后,入口结点便指向None
,也就变成了当前链表的尾结点。因此遍历完链表后,得到的pre
即为入口结点。注意,该方法破坏了原来的链表结构,需要根据题目要求决定是否可以使用。时间复杂度为
O
(
n
)
O(n)
O(n),空间复杂度为
O
(
1
)
O(1)
O(1).
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def EntryNodeOfLoop(self, pHead):
# write code here
if not pHead or not pHead.next:
return None
pre = pHead
p = pHead.next
while p:
pre.next = None
pre = p
p = p.next
return pre
思路二:公式法
假设有两个指针fast
、slow
,分别以
2
、
1
2、1
2、1的速度同时开始遍历链表。那么当两个指针相遇时,必然有如下关系:
S
f
a
s
t
=
2
∗
S
s
l
o
w
S_{fast} = 2 * S_{slow}
Sfast=2∗Sslow
其中:
S
f
a
s
t
=
∣
A
C
∣
+
m
∗
∣
C
C
∣
+
∣
C
B
∣
,
m
为
快
指
针
经
过
的
圈
数
S_{fast} = |AC| + m*|CC| + |CB|, m为快指针经过的圈数
Sfast=∣AC∣+m∗∣CC∣+∣CB∣,m为快指针经过的圈数
S
s
l
o
w
=
∣
A
C
∣
+
n
∗
∣
C
C
∣
+
∣
C
B
∣
,
n
为
慢
指
针
经
过
的
圈
数
S_{slow}=|AC|+n*|CC| + |CB|, n为慢指针经过的圈数
Sslow=∣AC∣+n∗∣CC∣+∣CB∣,n为慢指针经过的圈数
通过上面三个式子我们可以得出,
∣
A
C
∣
=
(
m
−
2
n
−
1
)
∗
∣
C
C
∣
+
∣
C
C
∣
−
∣
C
B
∣
|AC| = (m-2n-1)*|CC|+|CC|-|CB|
∣AC∣=(m−2n−1)∗∣CC∣+∣CC∣−∣CB∣
当两个指针第一次相遇的时候有
m
=
1
m=1
m=1,
n
=
0
n=0
n=0,此时有
∣
A
C
∣
=
∣
C
C
∣
−
∣
C
B
∣
=
∣
B
C
∣
|AC|=|CC|-|CB|=|BC|
∣AC∣=∣CC∣−∣CB∣=∣BC∣
这说明了相遇点和头结点到入口点的距离相等。
代码的思路是先判断有没有环,如果有环,则找出相遇点,如果没有环,则直接返回None。此时如果跳出循环则说明,两个指针在相遇点相遇了,这时候再根据相遇点到入口点的距离与头结点到入口点的距离相等来找到入口点。时空复杂度同方法一。
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def EntryNodeOfLoop(self, pHead):
# write code here
if not pHead or not pHead.next or not pHead.next.next:
return None
fast = pHead.next.next
slow = pHead.next
# 判断是否有环,若有环最终fast=slow=相遇点
while fast != slow:
if fast.next and fast.next.next:
fast = fast.next.next
slow = slow.next
else:
return None
# 寻找入口点
fast = pHead
while fast != slow:
fast = fast.next
slow = slow.next
return fast
Tips:
这里面fast
和slow
的初始化方式是相当于手动让两个指针各走了一步,主要是为了while
循环的判断更容易写。
54. 删除链表中重复的结点
题目描述
知识点:链表
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路
思路一
首先借助一个字典统计结点值出现的次数;第二次再遍历时根据需要删除重复结点。该方法时间复杂度为 O ( n ) O(n) O(n),空间复杂度最坏为 O ( n ) O(n) O(n)。但是这里几乎没有用到链表有序的已知条件。
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteDuplication(self, pHead):
# write code here
if not pHead or not pHead.next:
return pHead
count = {}
p = pHead
while p:
count[p.val] = count.get(p.val, 0) + 1
p = p.next
pre, p = None, pHead
while p:
if count[p.val] > 1:
# 需要删除第一个结点
if not pre:
pre = p
p = p.next
# 删除第一个结点
pre.next = None
# 第一个结点被删除了pre还没有进入链表
pre = None
# 第一个结点被删除了,调整开始结点位置
pHead = p
# 删除的不是第一个结点
else:
p = p.next
pre.next = p
# 不需要删除
else:
pre = p
p = p.next
return pHead
思路二
思路二主要抓住了两个点:
- 创建一个辅助头结点,主要是为了防止链表第一个结点就是需要删除的元素时方便操作;
- 链表是有序的,因此需要删除的元素一定是相邻的。
使用两个指针,一前一后,当后面的指针发现自己是重复结点时,就一直往后遍历直到遇到第一个和需要删除结点不同的结点,此时再利用前指针把中间所有的重复结点直接删除。该方法时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)。
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteDuplication(self, pHead):
# write code here
first = ListNode(-1)
first.next = pHead
pre = first
p = pHead
while p and p.next:
# p和随后的几个重复数字需要删除
if p.val == p.next.val:
tmp = p.val
# p一直走到第一个不需要删除的数字即p.val!=tmp
while p and p.val == tmp:
p = p.next
pre.next = p
# 不需要删除
else:
pre = p
p = p.next
return first.next