Leetcode 587. 安装栅栏
1.题目基本信息
1.1.题目描述
给定一个数组 trees,其中 trees[i] = [xi, yi] 表示树在花园中的位置。
你被要求用最短长度的绳子把整个花园围起来,因为绳子很贵。只有把 所有的树都围起来,花园才围得很好。
返回恰好位于围栏周边的树木的坐标。
1.2.题目地址
https://leetcode.cn/problems/erect-the-fence/description/
2.解题方法
2.1.解题思路
求凸包算法:andraw算法、Graham算法、jarvis算法
2.2.解题步骤
andraw算法(O(nlogn))
-
第一步,特殊情况处理。如果nodes的长度小于等于3,则结点都在凸包中,直接返回即可
-
第二步,定义求叉积函数cross,函数功能:求node1->node2和node2->node3两向量的叉积;根据叉积性质,如果axb二维叉积标量小于0,则向量b在向量a的右侧
-
第三步,将nodes按坐标位置进行升序排列
-
第四步,构建维护变量。stack维护遍历到的结点集合的凸包;visited维护stack中已经存在的结点,避免在求上半部分的凸包时重复(visited初始化时不标记起始结点,因为最后求上半闭包时也需要将起始结点入栈)
-
第五步,求凸包的下半部分。正序枚举nodes[1:],在stack长度大于等于2,且stack[-1]->nodes[i]的向量在stack[-2]->stack[-1]向量的右边时(叉积判断),不断循环从stack中弹出结点;同时在循环中和循环后更新维护变量stack和visited
-
第六步,求凸包的上半部分。逆序枚举nodes[:i-1],对于没有访问过的结点,在stack长度大于等于凸包下半部分长度时,且stack[-1]->nodes[i]的向量在stack[-2]->stack[-1]向量的右边时(叉积判断),不断循环从stack中弹出结点;同时在循环中和循环后更新维护变量stack和visited
-
第七步,将stack栈顶的起始结点进行弹出;根据stack中的结点索引构建凸包,返回即可
graham算法(O(nlogn))
-
第一步,特殊情况处理。如果nodes的长度小于等于3,则结点都在凸包中,直接返回即可
-
第二步,定义求叉积函数cross,函数功能:求node1->node2和node2->node3两向量的叉积;根据叉积性质,如果axb二维叉积标量小于0,则向量b在向量a的右侧
-
2.1.计算叉积
-
2.2.计算距离的平方
-
-
第三步,获取最底部的结点bottom,并将bottom结点置换到nodes的第一个位置
-
第四步,将nodes中各个结点进行角排序,通过叉积的正负来比较大小,叉积相同则通过与nodes[0]的距离排序
-
第五步,对于凸包上的最后一条边,如果上面存在共线,那么距离需要从大到小排列,所以需要翻转
- 5.1.将nodes[low,...,high]中结点进行翻转
-
第六步,正序枚举nodes[1:],在stack长度大于等于2,且stack[-1]->nodes[i]的向量在stack[-2]->stack[-1]向量的右边时(叉积判断),不断循环从stack中弹出结点;同时在循环中和循环后更新维护变量stack
-
第七步,根据stack中的结点索引构建凸包,返回即可
jarvis算法(O(n^2))
-
第一步,特殊情况处理。如果nodes的长度小于等于3,则结点都在凸包中,直接返回即可
-
第二步,定义求叉积函数cross。函数功能:求node1->node2和node2->node3两向量的叉积;根据叉积性质,如果axb二维叉积标量小于0,则向量b在向量a的右侧
-
第三步,获取最左边的最下面一个结点在nodes中的索引leftMost
-
第四步,构建维护变量。result数组存储凸包中已经找到的结点;visited维护各个结点是否已经在result中;p维护最后找到的一个凸包结点在nodes中的位置
-
第五步,循环遍历,寻找凸包上的结点添加到result数组中,并更新维护变量
-
5.1.找到一个结点q,使其余结点都在p->q向量的左边
-
5.2.找到q后,判断是否存在没有访问过且与p->q向量共线的结点,如果存在则将结点i添加到result数组中
-
5.3.如果q没有访问过,则将结点q添加到result结果数组中,并更新维护变量visited
-
5.4.将结点q赋值到p变量中,继续循环寻找下一个q结点;直到回到leftMost结点结束
-
-
第六步,返回结果result
3.解题代码
andraw算法代码
# ==> Andrew算法(求凸包算法)(O(nlogn))
def andrewConvexHull(nodes:list[list[int]]) -> list[list[int]]:
n = len(nodes)
# 第一步,特殊情况处理。如果nodes的长度小于等于3,则结点都在凸包中,直接返回即可
if n < 4:
return nodes
# 第二步,定义求叉积函数cross,函数功能:求node1->node2和node2->node3两向量的叉积;根据叉积性质,如果axb二维叉积标量小于0,则向量b在向量a的右侧
def cross(node1:list[int], node2:list[int], node3:list[int]) -> int:
x1, y1 = node1
x2, y2 = node2
x3, y3 = node3
return (x2 - x1) * (y3 - y2) - (x3 - x2) * (y2 - y1)
# 第三步,将nodes按坐标位置进行升序排列
nodes.sort()
# 第四步,构建维护变量。stack维护遍历到的结点集合的凸包;visited维护stack中已经存在的结点,避免在求上半部分的凸包时重复(visited初始化时不标记起始结点,因为最后求上半闭包时也需要将起始结点入栈)
stack = [0]
visited = [False] * n
# 第五步,求凸包的下半部分。正序枚举nodes[1:],在stack长度大于等于2,且stack[-1]->nodes[i]的向量在stack[-2]->stack[-1]向量的右边时(叉积判断),不断循环从stack中弹出结点;同时在循环中和循环后更新维护变量stack和visited
for i in range(1, n):
while len(stack) > 1 and cross(nodes[stack[-2]], nodes[stack[-1]], nodes[i]) < 0:
item = stack.pop()
visited[item] = False
stack.append(i)
visited[i] = True
# 第六步,求凸包的上半部分。逆序枚举nodes[:i-1],对于没有访问过的结点,在stack长度大于等于凸包下半部分长度时,且stack[-1]->nodes[i]的向量在stack[-2]->stack[-1]向量的右边时(叉积判断),不断循环从stack中弹出结点;同时在循环中和循环后更新维护变量stack和visited
m = len(stack) # 凸包下半部分的结点个数
for i in range(n - 2, -1, -1):
if not visited[i]:
while len(stack) > m and cross(nodes[stack[-2]], nodes[stack[-1]], nodes[i]) < 0:
item = stack.pop()
visited[item] = False
stack.append(i)
visited[i] = True
# 第七步,将stack栈顶的起始结点进行弹出;根据stack中的结点索引构建凸包,返回即可
stack.pop()
return [nodes[i] for i in stack]
class Solution:
# 思路1:andraw算法
def outerTrees1(self, trees: List[List[int]]) -> List[List[int]]:
convexHull = andrewConvexHull(trees)
return convexHull
graham算法代码
# ==> Graham算法(求凸包算法)(O(nlogn))
from functools import cmp_to_key
def grahamConvexHull(nodes:list[list[int]]) -> list[list[int]]:
n = len(nodes)
# 第一步,特殊情况处理。如果nodes的长度小于等于3,则结点都在凸包中,直接返回即可
if n < 4:
return nodes
# 第二步,定义求叉积函数cross,函数功能:求node1->node2和node2->node3两向量的叉积;根据叉积性质,如果axb二维叉积标量小于0,则向量b在向量a的右侧
# 2.1.计算叉积
def cross(node1:list[int], node2:list[int], node3:list[int]) -> int:
x1, y1 = node1
x2, y2 = node2
x3, y3 = node3
return (x2 - x1) * (y3 - y2) - (x3 - x2) * (y2 - y1)
# 2.2.计算距离的平方
def dist2(node1:list[int], node2:list[int]) -> int:
x1, y1 = node1
x2, y2 = node2
return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)
# 第三步,获取最底部的结点bottom,并将bottom结点置换到nodes的第一个位置
bottom = 0
for i in range(1, n):
if nodes[i][1] < nodes[bottom][1]:
bottom = i
nodes[bottom], nodes[0] = nodes[0], nodes[bottom]
# 第四步,将nodes中各个结点进行角排序,通过叉积的正负来比较大小,叉积相同则通过与nodes[0]的距离排序
def compare(node1:list[int], node2:list[int]) -> int:
diff = -cross(nodes[0], node1, node2)
return diff if diff else dist2(nodes[0], node1) - dist2(nodes[0], node2)
nodes[1:] = sorted(nodes[1:], key = cmp_to_key(compare))
# 第五步,对于凸包上的最后一条边,如果上面存在共线,那么距离需要从大到小排列,所以需要翻转
right = n - 1
while right >= 0 and cross(nodes[0], nodes[n - 1], nodes[right]) == 0:
right -= 1
low, high = right + 1, n - 1
# 5.1.将nodes[low,...,high]中结点进行翻转
while low < high:
nodes[low], nodes[high] = nodes[high], nodes[low]
low += 1
high -= 1
# 第六步,正序枚举nodes[1:],在stack长度大于等于2,且stack[-1]->nodes[i]的向量在stack[-2]->stack[-1]向量的右边时(叉积判断),不断循环从stack中弹出结点;同时在循环中和循环后更新维护变量stack
stack = [0]
for i in range(1, n):
while len(stack) > 1 and cross(nodes[stack[-2]], nodes[stack[-1]], nodes[i]) < 0:
stack.pop()
stack.append(i)
# 第七步,根据stack中的结点索引构建凸包,返回即可
return [nodes[i] for i in stack]
class Solution:
# 思路3:Graham算法
def outerTrees(self, trees: List[List[int]]) -> List[List[int]]:
convexHull = grahamConvexHull(trees)
return convexHull
jarvis算法代码
# ==> Jarvis算法(求凸包算法)(O(n^2))
def jarvisConvexHull(nodes:list[list[int]]) -> list[list[int]]:
n = len(nodes)
# 第一步,特殊情况处理。如果nodes的长度小于等于3,则结点都在凸包中,直接返回即可
if n < 4:
return nodes
# 第二步,定义求叉积函数cross。函数功能:求node1->node2和node2->node3两向量的叉积;根据叉积性质,如果axb二维叉积标量小于0,则向量b在向量a的右侧
def cross(node1:list[int], node2:list[int], node3:list[int]) -> int:
x1, y1 = node1
x2, y2 = node2
x3, y3 = node3
return (x2 - x1) * (y3 - y2) - (x3 - x2) * (y2 - y1)
# 第三步,获取最左边的最下面一个结点在nodes中的索引leftMost
leftMost = 0
for i in range(n):
if nodes[i][0] < nodes[leftMost][0] or (nodes[i][0] == nodes[leftMost][0] and nodes[i][1] < nodes[leftMost][1]):
leftMost = i
# 第四步,构建维护变量。result数组存储凸包中已经找到的结点;visited维护各个结点是否已经在result中;p维护最后找到的一个凸包结点在nodes中的位置
result = []
visited = [False] * n
p = leftMost
# 第五步,循环遍历,寻找凸包上的结点添加到result数组中,并更新维护变量
while True:
# 5.1.找到一个结点q,使其余结点都在p->q向量的左边
q = (p + 1) % n
for j, node in enumerate(nodes):
if cross(nodes[p], nodes[q], node) < 0:
q = j
# 5.2.找到q后,判断是否存在没有访问过且与p->q向量共线的结点,如果存在则将结点i添加到result数组中
for i in range(n):
if not visited[i] and i != p and i != q and cross(nodes[i], nodes[p], nodes[q]) == 0:
result.append(nodes[i])
visited[i] = True
# 5.3.如果q没有访问过,则将结点q添加到result结果数组中,并更新维护变量visited
if not visited[q]:
result.append(nodes[q])
visited[q] = True
# 5.4.将结点q赋值到p变量中,继续循环寻找下一个q结点;直到回到leftMost结点结束
p = q
if p == leftMost:
break
# 第六步,返回结果result
return result
class Solution:
# 思路2:jarvis算法
def outerTrees2(self, trees: List[List[int]]) -> List[List[int]]:
convexHull = jarvisConvexHull(trees)
return convexHull
4.执行结果


浙公网安备 33010602011771号