指针
- 快慢指针
- 顾名思义,它的核心思想是,有2个指针,一个走的快,一个走的慢,以此来解决问题
- 例如:判断链表是否有环
-
思路如下:
首先定义2个指针,一个是快指针,一个是慢指针,快指针第一次指向第一个元素,慢指针第一次也指向第一个元素,快指针先去探索因为它叫快指针,所以要一次走两步,而慢指针呢,一次就走一步.
我们要判断链表是否有环,有2种可能,一种就是有环,而另一种就是没环.
做这类题目之前呢,要先把边界考虑好,如果本身传进来的就是个空链表,那我们是不是就不用判断啦,返回的结果就是没有环呗,如果链表就只有1个结点,那是不是也不用判断呀,直接返回空就可以啦.
所以呀,我们是不是要看第2个结点以后包含第2个结点,那是不是就要用循环了呀,那我们前面已经分析啦,链表为空,或者链表只有一个结点的时候,直接返回没有环就可以了.
那循环条件就是当快指针第一个结点并且快指针第二个结点都不为空的时候进行循环,在不知道循环次数的前提下用while循环语句会好一些,上面都是说链表没有环,那怎样才能判断链表有环呢,这里给你讲个故事你们就懂啦.
相信大家都参加过体测吧,那男生是1000米,女生是800米,是不是有这样一个现象,就是第一名一般都会套圈最后一名,也就是,第一名都会和最后一名相遇,说到这里是不是你们就懂了呢,对快指针就相当于是第一名,慢指针就相当于是最后一名,只要快指针等于慢指针了,那不就证明链表是有环存在的吗,反过来,直到循环结束,快指针都不等于慢指针,不就说明,链表是没有环的吗,所以看快指针和慢指针相不相等,如果相等,就说明有环,如果到了循环结束也不行等,就说明没有环
- python代码如下:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkList: # 解构
def huan(self, head: Node):
fast = slow = head
while fast and fast.next:
fast, slow = fast.next.next, slow.next
if fast == slow:
return True
return False
n1 = Node(1)
n2 = Node(2)
n3 = Node(3)
n4 = Node(4)
n1.next = n2
n2.next = n3
n3.next = n4
n4.next = n1
ll = LinkList()
print(f"链表是否有环: {ll.huan(n1)}")
入环点
- 说到入环点呢,它和链表是否有环非常相似,就是在链表是否有环的基础上,也就是说,当链表的快指针和慢指针相遇也就是相等了,让慢指针指向链表头部,在进行循环,直到下一次相遇,慢指针指向的结点,就是入环点了.
- 如图,设整个链表长度为L,环长度为R,且距离具有方向性,例如CB是C点到B点的距离,BC是B点到C点的距离,CB!=BC。当证明有环时,fast和slow都顺时针到了B点,则此时:
- slow走的距离:AC+CB
- fast走的距离:AC+k*R+CB(k=0,1,2…)R:表示圈数
- 由于fast每次走2个节点,slow每次走1个节点,所以:
- 2(AC+CB) = AC+k*R+CB
- AC+CB = k*R
- AC+CB = (k-1)*R+R
- AC = (k-1)*R+R-CB
- AC = (k-1)*R+BC
- 从最终的表达式可以看出来,AC的距离等于绕环若干圈后再加上BC的距离,也就是说慢指针从A点出发以速度1前进、快指针从B点出发以速度1前进,则慢指针到C点时,快指针也必然到了。
- python 代码如下:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def __repr__(self):
return f"Node({self.data})"
class LinkList:
def huan(self, head):
fast = slow = head
f = 0
while fast and fast.next:
fast, slow = fast.next.next, slow.next
if fast == slow:
f = 1
break
if f:
slow = head
while slow != fast:
slow, fast = slow.next, fast.next
return slow
n1 = Node(1)
n2 = Node(2)
n3 = Node(3)
n4 = Node(4)
n1.next = n2
n2.next = n3
n3.next = n4
n4.next = n2
ll = LinkList()
print(f"入环点为: {ll.huan(n1)}")
更相减损术和辗转相除法(合并)
- 思路如下:
更相减损术是出自《九章算术》的一种求最大公约数的算法,它原本是为约分而设计的,但它适用于任何需要求最大公约数的场合。《九章算术》是中国古代的数学专著,其中的“更相减损术”可以用来求两个数的最大公约数,原文是:
可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。
白话文译文:
(如果需要对分数进行约分,那么)可以折半的话,就折半(也就是用2来约分)。如果不可以折半的话,那么就比较分母和分子的大小,用大数减去小数,互相减来减去,一直到减数与差相等为止,用这个相等的数字来约分。
我们用它求最大公约数如下: - 更相减损数 a>b a-b=c c与b的最大公约数即为a与b的最大公约数
- 缺点:更相减损数缺陷: 差值比较大
欧几里得算法又称辗转相除法,是指用于计算两个非负整数a,b的最大公约数。应用领域有数学和计算机两个方面。计算公式gcd(a,b) = gcd(b,a mod b)
欧几里得算法是用来求两个正整数最大公约数的算法。古希腊数学家欧几里得在其著作《The Elements》中最早描述了这种算法,所以被命名为欧几里得算法。
- 辗转相除法 a>b a%b=c c与b的最大公约数即为a与b的最大公约数
- 缺点:辗转相除法缺陷: 2个比较大的数取余会慢
那我们就会想,能不能把它们两个合起来呢,哎,当然可以的啦,接下来,就谈一谈合并的思路:
- 最优方法:把辗转相除法和更相减损法的优势结合起来,在更相减算术的基础上使用移位运算
- 对于给定的正整数a和b,不难得到如下的结论。其中gcb(a,b)的意思是a,b的最大公约数函数:
- 当a和b均为偶数,gcb(a,b) = 2gcb(a/2, b/2) = 2gcb(a>>1, b>>1)
- 当a为偶数,b为奇数,gcb(a,b) = gcb(a/2, b) = gcb(a>>1, b)
- 当a为奇数,b为偶数,gcb(a,b) = gcb(a, b/2) = gcb(a, b>>1)
- 当a和b均为奇数,利用更相减损术运算一次,gcb(a,b) = gcb(b, a-b), 此时a-b必然是偶数,又可以继续进行移位运算。
这样合并就完成啦
python 代码:
def greatest_common_divisor(a: int, b: int) -> int:
if a < b:
a, b = b, a
mod = a % b
result = b
if mod == 0:
result = b
else:
if not (a & 1) and not (b & 1):
result = greatest_common_divisor(a >> 1, b >> 1) << 1
elif not (a & 1) and b & 1:
result = greatest_common_divisor(a >> 1, b)
elif a & 1 and not b & 1:
result = greatest_common_divisor(a, b >> 1)
elif a & 1 and b & 1:
result = greatest_common_divisor(b, a - b)
return result
if __name__ == '__main__':
a = 88
b = 24
result = greatest_common_divisor(a, b)
print(result)