难度: Hard
Submission
运行时间: 132 ms
内存: 18.0 MB
class Solution: def outerTrees(self, trees: List[List[int]]) -> List[float]: n = len(trees) # 随机化点防止恶心用例 random.shuffle(trees) # 求两点距离的平方 def getDist(a,b,x,y): return (a-x)*(a-x)+(b-y)*(b-y) # 根据两点求圆心和半径 def getCircleBy2(i,j): a,b = trees[i] x,y = trees[j] rx,ry = (a+x)/2,(b+y)/2 r = getDist(rx,ry,x,y) return [rx,ry,r] # 根据三个点求圆心和半径,保证三点不共线 def getCircleBy3(i,j,h): x,y = trees[i] a,b = trees[j] p,q = trees[h] if a == x: k1 = float('inf') else: k1 = (y-b)/(x-a) if x == p: k2 = float('inf') else: k2 = (q-y)/(p-x) # 同斜率,三点共线 if k1 == k2 or abs(k1-k2) < 1e-7: tmp = [] tmp.append(getCircleBy2(i,j)) tmp.append(getCircleBy2(i,h)) tmp.append(getCircleBy2(j,h)) tmp.sort(key = lambda x:x[2]) return tmp[-1] # 不同斜率,选取两直线,其中垂线斜率要存在的,然后求出两中垂线交点,即为圆心 # 保证中垂线有斜率 if b == y: x,y,p,q = p,q,x,y elif y == q: a,b,x,y = x,y,a,b # a,b,x,y 中垂线 k1 = -(x-a)/(y-b) tmpx,tmpy = (a+x)/2,(b+y)/2 b1 = tmpy - tmpx*k1 # x,y,p,q 中垂线 k2 = -(x-p)/(y-q) tmpx,tmpy = (p+x)/2,(q+y)/2 b2 = tmpy - tmpx*k2 # y = k1*x+b1 # y = k2*x+b2 # (k1-k2)x + b1 - b2 = 0 X = (b2-b1)/(k1-k2) Y = k1*X+b1 r = ((X-x)*(X-x)+(Y-y)*(Y-y)) return [X,Y,r] # 初始化,第一个点为圆心,半径为0 x,y = trees[0] r = 0 # welzl 算法,当前k个点在圆内,若第k+1个点不在圆内,那么第k+1个点必在圆上 for i in range(1,n): a,b = trees[i] # 在圆内 if getDist(x,y,a,b) <= r: continue # 在圆外,那么i点必定在圆上 # 重新找圆,已知i必在圆上 x,y = trees[i] r = 0 # 找第二个必在圆上的点 for j in range(i): a,b = trees[j] # 在圆上 if getDist(x,y,a,b) <= r: continue # 在圆外,那么j必在圆上,已知i也必在圆上,那就两点为圆直径,更新圆 x,y,r = getCircleBy2(i,j) # 找第三个必在圆上的点 for h in range(j): a,b = trees[h] if getDist(x,y,a,b) <= r: continue # 在圆外,那么h必在圆上,即i,j,h必在圆上,更新圆 x,y,r = getCircleBy3(i,j,h) return [x,y,r**0.5]
Explain
本题解采用了Welzl算法,这是一个递归算法,用于找到覆盖给定点集的最小圆。算法的基本思想是,开始假设没有点在圆内,逐步添加点,并调整圆的位置和大小以包含所有点。如果添加的点已在圆内,则不需要调整;如果不在圆内,该点必在新的圆的边界上,此时需要重新计算圆。算法具体包括以下步骤: 1. 随机化点的顺序,以避免特定边界情况影响性能。 2. 使用两点确定一个圆,计算圆心和半径。 3. 使用三点确定一个圆,确保这三点不共线,计算圆心和半径。 4. 递归地检查每个点,如果点在当前圆外,则更新圆,使其包括该点,这可能需要找到新的两点或三点定圆。
时间复杂度: O(n^3)
空间复杂度: O(n)
class Solution: def outerTrees(self, trees: List[List[int]]) -> List[float]: n = len(trees) # 随机化点防止恶心用例 random.shuffle(trees) # 求两点距离的平方 def getDist(a,b,x,y): return (a-x)*(a-x)+(b-y)*(b-y) # 根据两点求圆心和半径 def getCircleBy2(i,j): a,b = trees[i] x,y = trees[j] rx,ry = (a+x)/2,(b+y)/2 r = getDist(rx,ry,x,y) return [rx,ry,r] # 根据三个点求圆心和半径,保证三点不共线 def getCircleBy3(i,j,h): x,y = trees[i] a,b = trees[j] p,q = trees[h] if a == x: k1 = float('inf') else: k1 = (y-b)/(x-a) if x == p: k2 = float('inf') else: k2 = (q-y)/(p-x) # 同斜率,三点共线 if k1 == k2 or abs(k1-k2) < 1e-7: tmp = [] tmp.append(getCircleBy2(i,j)) tmp.append(getCircleBy2(i,h)) tmp.append(getCircleBy2(j,h)) tmp.sort(key = lambda x:x[2]) return tmp[-1] # 不同斜率,选取两直线,其中垂线斜率要存在的,然后求出两中垂线交点,即为圆心 # 保证中垂线有斜率 if b == y: x,y,p,q = p,q,x,y elif y == q: a,b,x,y = x,y,a,b # a,b,x,y 中垂线 k1 = -(x-a)/(y-b) tmpx,tmpy = (a+x)/2,(b+y)/2 b1 = tmpy - tmpx*k1 # x,y,p,q 中垂线 k2 = -(x-p)/(y-q) tmpx,tmpy = (p+x)/2,(q+y)/2 b2 = tmpy - tmpx*k2 # y = k1*x+b1 # y = k2*x+b2 # (k1-k2)x + b1 - b2 = 0 X = (b2-b1)/(k1-k2) Y = k1*X+b1 r = ((X-x)*(X-x)+(Y-y)*(Y-y)) return [X,Y,r] # 初始化,第一个点为圆心,半径为0 x,y = trees[0] r = 0 # welzl 算法,当前k个点在圆内,若第k+1个点不在圆内,那么第k+1个点必在圆上 for i in range(1,n): a,b = trees[i] # 在圆内 if getDist(x,y,a,b) <= r: continue # 在圆外,那么i点必定在圆上 # 重新找圆,已知i必在圆上 x,y = trees[i] r = 0 # 找第二个必在圆上的点 for j in range(i): a,b = trees[j] # 在圆上 if getDist(x,y,a,b) <= r: continue # 在圆外,那么j必在圆上,已知i也必在圆上,那就两点为圆直径,更新圆 x,y,r = getCircleBy2(i,j) # 找第三个必在圆上的点 for h in range(j): a,b = trees[h] if getDist(x,y,a,b) <= r: continue # 在圆外,那么h必在圆上,即i,j,h必在圆上,更新圆 x,y,r = getCircleBy3(i,j,h) return [x,y,r**0.5]
Explore
Welzl算法通过递归方式构建最小包围圆,如果处理的点序列恰好是一种极端或特定的顺序,比如按照某种几何关系排列,可能导致性能降低,因为算法可能需要更频繁地调整已构建的圆。随机化点的顺序可以使算法的性能表现更加均匀,更不易受到特定点序列的影响,从而提高算法的平均性能和稳定性。这种随机化有助于避免最坏情况的发生,尤其是在实际应用中,数据可能具有某种未预见的有序性。
当三点不共线时,可以通过求解这三点的外接圆来确定圆心和半径。首先,利用两点间的中点和斜率的负倒数(如果存在)来确定两条中垂线的方程。接着,求这两条中垂线的交点,该交点即为所求圆的圆心。圆的半径则是圆心到任一点的距离。具体计算时,需要处理斜率不存在的情况,即两点垂直排列,这时中垂线将是水平或垂直线。通过解方程组来找到交点,并计算半径。
当算法检测到三点共线时,无法直接使用标准的外接圆计算方法,因为共线的三点无法确定一个唯一的圆。在这种情况下,算法的处理策略是比较这三点对应的三个由任两点确定的圆(每两点可确定一个最小圆即其直径的圆)。算法将计算这三个圆的半径,并选择半径最大的圆作为结果。这保证了包含所有三点的圆的存在,虽然这不一定是最小圆,但确保了算法可以继续进行下去。