特别注意:
数组O(1) 是访问指定下标位置元素,这是O(1), 看一个数在不在数字里, 时间是O(n)的。插入和删除都是O(n)的
链表:
链表的 查询时间是O(n), 插入删除是O(1)
https://blog.csdn.net/DeskyAki/article/details/100831860
上面僕のブログ实现了链表的构造, 数组转链表, 打印链表, 链表的尾加, 插入,删除, 逆序,两两对调, 去除重复数据的基本功能。
1 如何找出链表中倒数第k个元素
如果是正序那就炒鸡简单,没得商量。
找倒数第k元素, 不假思索的就是两次遍历, 一次遍历统计长度n,在遍历一次找到第n-k个元素,虽然遍历了两次, 但时间复杂度也是o(n)
另一种方法就十分巧妙了,利用快慢指针,快指针比慢指针超前k个位置, 同时往下走, 直到快指针走完,慢指针的位置就是倒数第k个位置, 下面的代码, 放进我的上面的blog直接就能用
def find_num(self, k):
"""
:param k: 倒数第k个位置
:return:
"""
p, p_fast = self.head, self.head
i = 0
while i < k and p_fast is not None:
p_fast = p_fast.next
i += 1
if i < k:
print("超出索引")
return
while p_fast is not None:
p_fast = p_fast.next
p = p.next
print(p.data)
2 将链表右旋k个位置
如 1,2,3,4,5 k=3 结果 3,4,5,1,2
也是快慢指针, 不过和上面有有些区别, 要找到倒数第k+1个数, 要p_fast.next:, 这个条件就能找到倒数第k+1个数
def change_k(self, k):
"""
链表右旋k, 1,2,3,4,5 k=2, 结果为 4,5,1,2,3
:param k:
:return:
"""
p, p_fast = self.head, self.head
i = 0
while p and i < k and p_fast is not None:
p_fast = p_fast.next
i += 1
if i < k:
print("超出索引")
return
while p_fast.next:
# 注意是p_fast.next
p_fast = p_fast.next
p = p.next
q = p.next
p.next = None
p_fast.next = self.head.next
# 因为是有头链表, 要想先next
self.head.next = q
3 判断一个链表是否有环
也是巧妙地利用快慢指针,同时从头节点出发, 慢指针一次走一步, 快指针一次走两步。如果快慢指针相等, 这里的相等肯定不是值相等了, 是实例的内存地址相等, 说明是一个实例,及两个指针相遇, 说明链表有环。
构造完有环链表, 打印的时候一直循环, 还以为敲错了, 一想可不一直打印因为有环, 没有指向None的, 只能是一直打印了。
打印结果 None->1->2->3->4->5->3->4->5->3->4->5->3->4-> 在3这个节点一直循环, 因为设置的是p.next = self.head.next.next.next
还有刚才脑子突然蹦出个想法, 如果链表没环会不会出bug? 瞬间就否定了自己的想法,因为是 while p and fast_p:
class LNode(object):
def __init__(self, data):
self.data = data
self.next = None
class LList(object):
def __init__(self):
self.head = None
def list_node(self, arr):
self.head = LNode(None)
p = self.head
for i in arr:
node = LNode(i)
p.next = node
p = p.next
p.next = self.head.next.next.next
# 构造有环链表
def print_node(self):
p = self.head
while p:
print(p.data, end="->")
p = p.next
def is_loop(self):
p, fast_p = self.head, self.head
while p and fast_p:
p = p.next
fast_p = fast_p.next.next
if fast_p == p:
print("True")
return fast_p
if __name__ == "__main__":
head = LList()
head.list_node([1, 2, 3, 4, 5])
head.is_loop()
4 一个链表有环, 确定环的入口
如上题, 环的入口就是3。
这道题的解法就很哲学了, 不是吾等凡人能想出来的,大概思路就是,
利用快慢指针找到它们相遇的节点,在搞两个指针,一个从头节点开始遍历, 一个从相遇的节点开始遍历, 速度都是1, 两个指针相遇的节点,就是入口节点。
我来斗胆说说推理过程:
前面不说了,就说为什么从相遇点开始遍历,在次相遇的点就是入口节点
慢指针走s步, 快指针2s部,第一次相遇时,设快指针在环里走n圈,环长为r, 则
2s = s + nr
所以 s = nr
设整个链表长L, 入口节点与第一次相遇点距离为x, 起点到入口点的距离为a, 则
a + x = nr
a + x = (n-1)r + r + L - a
a = (n-1)r + (L-a-x)
(L-a-x) 为相遇点到入口点的距离,起点到入口点的距离为a = (n-1)r + (L-a-x),不管走了几圈,第一次相遇的节点都是一个位置, 所以就说明起点到入口节点的距离 == 相遇点到入口节点的距离, 下面附图:
mmp, 知道了解法, 写起来真是爽,
class LNode(object):
def __init__(self, data):
self.data = data
self.next = None
class LList(object):
def __init__(self):
self.head = None
def list_node(self, arr):
self.head = LNode(None)
p = self.head
for i in arr:
node = LNode(i)
p.next = node
p = p.next
p.next = self.head.next.next.next
# 构造有环链表
def is_loop(self):
"""
找到相遇点
:return:
"""
p, fast_p = self.head, self.head
while p and fast_p:
p = p.next
fast_p = fast_p.next.next
if fast_p == p:
return fast_p
def find_it(self):
"""
找到入口节点
:return:
"""
p = self.head
# p 是起点
q = self.is_loop()
# q 相遇点
while q is not p:
q = q.next
p = p.next
print(p.data)
if __name__ == "__main__":
head = LList()
head.list_node([1, 2, 3, 4, 5])
head.find_it()
5 两两对调链表
def swap_node(self):
"""
链表的两两对调
1,2,3,4,5 --> None->2->1->4->3->5->
"""
p = self.head
cur = p.next
pre = p
while cur and cur.next:
next = cur.next.next
pre.next = cur.next
cur.next.next = cur
cur.next = next
pre = cur
cur = next
self.head = p