当前位置: 首页 > news >正文

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 = node1x2, y2 = node2x3, y3 = node3return (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和visitedfor 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] = Falsestack.append(i)visited[i] = True# 第六步,求凸包的上半部分。逆序枚举nodes[:i-1],对于没有访问过的结点,在stack长度大于等于凸包下半部分长度时,且stack[-1]->nodes[i]的向量在stack[-2]->stack[-1]向量的右边时(叉积判断),不断循环从stack中弹出结点;同时在循环中和循环后更新维护变量stack和visitedm = 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] = Falsestack.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 = node1x2, y2 = node2x3, y3 = node3return (x2 - x1) * (y3 - y2) - (x3 - x2) * (y2 - y1)# 2.2.计算距离的平方def dist2(node1:list[int], node2:list[int]) -> int:x1, y1 = node1x2, y2 = node2return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)# 第三步,获取最底部的结点bottom,并将bottom结点置换到nodes的第一个位置bottom = 0for i in range(1, n):if nodes[i][1] < nodes[bottom][1]:bottom = inodes[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 - 1while right >= 0 and cross(nodes[0], nodes[n - 1], nodes[right]) == 0:right -= 1low, high = right + 1, n - 1# 5.1.将nodes[low,...,high]中结点进行翻转while low < high:nodes[low], nodes[high] = nodes[high], nodes[low]low += 1high -= 1# 第六步,正序枚举nodes[1:],在stack长度大于等于2,且stack[-1]->nodes[i]的向量在stack[-2]->stack[-1]向量的右边时(叉积判断),不断循环从stack中弹出结点;同时在循环中和循环后更新维护变量stackstack = [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 = node1x2, y2 = node2x3, y3 = node3return (x2 - x1) * (y3 - y2) - (x3 - x2) * (y2 - y1)# 第三步,获取最左边的最下面一个结点在nodes中的索引leftMostleftMost = 0for 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] * np = leftMost# 第五步,循环遍历,寻找凸包上的结点添加到result数组中,并更新维护变量while True:# 5.1.找到一个结点q,使其余结点都在p->q向量的左边q = (p + 1) % nfor 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结果数组中,并更新维护变量visitedif not visited[q]:result.append(nodes[q])visited[q] = True# 5.4.将结点q赋值到p变量中,继续循环寻找下一个q结点;直到回到leftMost结点结束p = qif p == leftMost:break# 第六步,返回结果resultreturn resultclass Solution:# 思路2:jarvis算法def outerTrees2(self, trees: List[List[int]]) -> List[List[int]]:convexHull = jarvisConvexHull(trees)return convexHull

4.执行结果

相关文章:

  • 针对面试- RabbitMQ消息队列篇
  • 基础编程题目集 7-1 厘米换算英尺英寸
  • java基础(面向对象高级部分)
  • 快速扩容VHD文件的DiskPart命令指南
  • 临床研究统计分析核心概念解析
  • 【AI智能体】Spring AI Tool 从使用到实战详解
  • day28JS+Node-JS打包工具Webpack
  • multiprocessing多进程使用案例
  • 使用Python,OpenCV,Tesseract-OCR对自己的运动数据图片进行识别及分析,并使用Matplotlib绘制配速图出来
  • 【b站计算机拓荒者】【2025】微信小程序开发教程 - chapter3 项目实践 -1 项目功能描述
  • 机器学习---特征降维
  • 运维Linux之Ansible详解学习(更新中)
  • Python应用运算符初解
  • Word转PDF--自动生成目录
  • 如何在uniapp H5中实现路由守卫
  • 【Vue3】(二)vue3语法详解:自定义泛型、生命周期、Hooks、路由
  • Linux C学习路线全概及知识点笔记1(仅供个人参考)
  • 【2025】基于Springboot + vue + 协同过滤算法实现的旅游推荐系统
  • 基于RK3576+FPGA+CODESYS工控板的运动控制模块方案
  • # 探索自然语言处理的奥秘:基于 Qwen 模型的文本分类与对话系统实现
  • 网站推广意义/自己如何开网站
  • 网站制作企业/建一个app平台的费用多少
  • 环保设备网站建设模板/企业网站营销的优缺点
  • 网站建设与管理计划/全媒体运营师报考条件
  • 9e做网站/新品牌进入市场的推广方案
  • 甘肃做网站找谁/域名注册官网