zltzlt 发表于 2020-4-16 12:09:05

Python:每日一题 377

本帖最后由 zltzlt 于 2020-4-16 12:53 编辑

今天的题目:

在一个二维的花园中,有一些用 (x, y) 坐标表示的树。

由于安装费用十分昂贵,所以要先用最短的绳子围起所有的树。

只有当所有的树都被绳子包围时,花园才能围好栅栏。找到正好位于栅栏边界上的树的坐标。

说明:
1. 输入的点没有顺序,输出的点也没有顺序要求。
2. 所有的树应当被围在一起,不能剪断绳子来包围树或者把树分成一组以上。
3. 花园至少有一棵树。
4. 所有树的坐标都是不同的。

示例 1:

输入:[, , , , , ]
输出:[, , , , ]
解释:

示例 2:

输入:[, , ]
输出:[, , ]
解释:

即使树都在一条直线上,也需要先用绳子包围它们。

{:10_298:}欢迎大家一起答题!{:10_298:}

March2615 发表于 2020-4-16 12:09:06

本帖最后由 March2615 于 2020-4-16 15:59 编辑

# 解题思路:
# 从最左边一棵树开始,逆时针绕着围
# 如果是逆时针转,则向量叉乘为正,否则,叉乘为负(可以想象成应该一直左转不能有右转)
# 叉乘要三个点,所以不足两个点的时候不管是不是为正,都要认为是边界上的
def daily377(trees: list) -> list:
    if len(trees) < 4:# 小于四棵树的时候,肯定都在边界上了
      return trees

    def cross(p1, p2, p3):# p1->p2 & p2->p3
      return (p1 - p2) * (p3 - p2) - (p1 - p2) * (p3 - p2)

    down = []
    for tree in sorted(trees):
      while len(down) >= 2 and cross(down[-2], down[-1], tree) < 0:
            down.pop()
      down.append(tree)

    up = []
    for tree in sorted(trees, reverse=True):
      while len(up) >= 2 and cross(up[-2], up[-1], tree) < 0:
            up.pop()
      up.append(tree)

    result = []
    for each in down[:-1] + up[:-1]:
      if each not in result:
            result.append(each)
    return result

不知不觉一下午过去了{:10_285:}

kinkon 发表于 2020-4-16 12:40:51

本帖最后由 kinkon 于 2020-4-16 18:16 编辑

复杂度有点高,希望能过吧,看看能不能过,做这种题型还是比较吃力
def f377(arr):
    n = len(arr)
    if n < 4:
      return arr
    arr = sorted(arr, key = lambda x: (x, x))
    temp = lambda o, x, y: (x - o) * (y - o) - (x - o) * (y - o)
    left, right = [], []
    for a in arr:
      while len(left) > 1 and temp(left[-2], left[-1], a) < 0:
            left.pop()
      left.append(a)
      
    for a in range(n -1, -1, -1):
      while len(right) > 1 and temp(right[-2], right[-1], arr) < 0:
            right.pop()
      right.append(arr)
    return left[:-1] + right[:-1]

skittles5 发表于 2020-4-16 13:32:01

直觉就是计算几何学中的求convex hull,贴个书上的伪代码,回头研究研究用python怎么实现

TJBEST 发表于 2020-4-16 14:58:58

本帖最后由 TJBEST 于 2020-4-16 18:49 编辑

来看看我的史诗巨作,历时1个小时20分钟
def fun377(points):
    def gcd(a,b):#计算最大公约数
      while b:
            a,b = b,a%b
      return a
    def getLine(point1,point2):#计算直线方程
      if point1 == point2:
            return (1,0,-point1)
      elif point1 == point2:
            return (0,1,-point1)
      else:
            a = point2 - point1
            b = point1 - point2
            c = point2*point1 - point1*point2
            if a < 0:
                a,b,c = -a,-b,-c
            if c == 0:
                T_ab = gcd(a,abs(b))
                return (a//T_ab,b//T_ab,0)
            else:
                T_ab = gcd(a,abs(b))
                T_ac = gcd(a,abs(c))
                T_abc = gcd(T_ab,T_ac)
                return (a//T_abc,b//T_abc,c//T_abc)
    def getH(Line,point):#计算高(不按实际,仅正负号判断是否一侧)
      return Line*point+Line*point+Line

    if len(points) < 4:
      return points

    totalSet = set(tuple(each) for each in points )#元组多好,不要用序列,不hash
    unKnown = totalSet.copy()#未知点集合
    onBorder = set()#边界点集合
    inBorder = set()#边界内部点集合
    LineSet = set()#直线集合 形式 ax+by+c (a,b,c)
    while unKnown:
      tempPoint = unKnown.pop()
      if (tempPoint in onBorder) or (tempPoint in inBorder):
            pass
      else:
            tempSet = totalSet - (inBorder | {tempPoint})
            while tempSet:
                innerPoint = tempSet.pop()
                tempLine = getLine(tempPoint,innerPoint)
                if tempLine in LineSet:
                  continue
                LineSet.add(tempLine)
                #计算是否一侧?
                H = [(getH(tempLine,each),each) for each in totalSet]#记录高和点
                MaxH = max(H)
                MinH = min(H)
                if MaxH*MinH >= 0:#在一侧即边界
                  onBorder |= set( for each in H if each == 0])
                  break#该点是边界点 就不用测了
                else:#最麻烦 去掉两端 都是in 两端无法判断
                  pointOnLine = for each in H if each == 0]
                  if pointOnLine != pointOnLine:
                         pointOnLine.sort(key = lambda each:each)
                  else:
                         pointOnLine.sort(key = lambda each:each)
                  inBorder |= set(pointOnLine)
                  if tempPoint in inBorder:
                        break
                  else:
                        tempSet = totalSet - (inBorder | {tempPoint,innerPoint})#得去掉 innerPoint避免死循环
            else:
                inBorder.add(tempPoint)
    result = []
    for each in onBorder:
      result.append(list(each))
    return result

TJBEST 发表于 2020-4-16 15:03:21

@zltzlt所有坐标都是整数吧?否则可能需要软判决。

kinkon 发表于 2020-4-16 15:21:53

本帖最后由 kinkon 于 2020-4-16 18:23 编辑

优化一下
def f377(arr):
    n = len(arr)
    if n < 4:
      return arr
    arr = sorted(arr, key = lambda x: (x, x))
    temp = lambda o, x, y: (x - o) * (y - o) - (x - o) * (y - o)         
    left, right = [], []
    for a in range(n):
      
      while len(left) > 1 and temp(left[-2], left[-1], arr) < 0:
            left.pop()
      left.append(arr)
      
      while len(right) > 1 and temp(right[-2], right[-1], arr[~a]) < 0:
            right.pop()
      right.append(arr[~a])
      
    return left[:-1] + right[:-1]

March2615 发表于 2020-4-16 15:50:01

kinkon 发表于 2020-4-16 15:21
@zltzlt 有没有重复坐标?

4.所有树的坐标都是不同的

TJBEST 发表于 2020-4-16 15:53:11

March2615 发表于 2020-4-16 15:50
4.所有树的坐标都是不同的

我觉得坐标都是整数吧,否则点在不在边界上 会出现问题。{:5_109:}

kinkon 发表于 2020-4-16 15:57:38

本帖最后由 kinkon 于 2020-4-16 18:25 编辑

@zltzlt 输出排序有要求吗?

March2615 发表于 2020-4-16 16:02:29

TJBEST 发表于 2020-4-16 15:53
我觉得坐标都是整数吧,否则点在不在边界上 会出现问题。

按照我自己的解题思路是不用考虑是不是整数的
回复不能传图片好像
我把第一个测试用例的改成是没问题的
也可能是我哪没想到吧

TJBEST 发表于 2020-4-16 16:09:50

March2615 发表于 2020-4-16 16:02
按照我自己的解题思路是不用考虑是不是整数的
回复不能传图片好像
我把第一个测试用例的改成

小数在编程语言中是存在问题的,需要软判决。因为如果你在过程中出现比大小 即> = <时是需要指定判决精度的 比如 某种精度下 3.1111和3.1110认为相等

TJBEST 发表于 2020-4-16 16:13:12

@zltzlt 我先按全是整数来做吧,否则软判决太麻烦,需要精度控制,题目中没说,我觉得应该是坐标整数

March2615 发表于 2020-4-16 16:14:46

TJBEST 发表于 2020-4-16 16:09
小数在编程语言中是存在问题的,需要软判决。因为如果你在过程中出现比大小 即> =

这我倒是没考虑到
不过这样测试用例也太难找了

受教了,下次注意

kinkon 发表于 2020-4-16 16:31:24

TJBEST 发表于 2020-4-16 16:13
@zltzlt 我先按全是整数来做吧,否则软判决太麻烦,需要精度控制,题目中没说,我觉得应该是坐标整数

这种题型应该不会出现有浮点数吧,我也是按整数来做的

zltzlt 发表于 2020-4-16 17:53:49

kinkon 发表于 2020-4-16 16:31
这种题型应该不会出现有浮点数吧,我也是按整数来做的

不会

zltzlt 发表于 2020-4-16 17:54:01

TJBEST 发表于 2020-4-16 16:13
@zltzlt 我先按全是整数来做吧,否则软判决太麻烦,需要精度控制,题目中没说,我觉得应该是坐标整数

是整数

zltzlt 发表于 2020-4-16 18:32:33

kinkon 发表于 2020-4-16 15:57
@zltzlt 输出排序有要求吗?

说明:
1. 输入的点没有顺序,输出的点也没有顺序要求。

whosyourdaddy 发表于 2020-4-16 20:11:37

def func377(arr):
    def distance( p,q):
      return (p-q) * (p-q) + (p-q) * (p- q)

    def bottom(arr):
      bottoms = arr
      for i in arr:
            if i<bottoms:
                bottoms = i
      return bottoms
    def cross(p1, p2, p3):# p1->p2 & p2->p3
      return (p1 - p2) * (p3 - p2) - (p1 - p2) * (p3 - p2)
    def jiaodu(a,b):
      c = (b-a)/(pow((b-a),2)+pow(b-a,2))
      return c
    n = len(arr)
    if n < 3: return arr
    stack = []
    temp = arr[:]
    bottoms = bottom(arr)
    temp.remove(bottoms)
    sorted(temp, key = lambda a: (jiaodu(bottoms,a),distance(bottoms,a)))
    temp.insert(0,bottoms)
    for i in range(1,n-1):
      b = cross(temp,temp,temp)
      if b<0:
            stack.append(temp)
            temp=temp
    for each in stack:
      arr.remove(each)
    return arr

zltzlt 发表于 2020-4-16 20:47:54

kinkon 发表于 2020-4-16 12:40
复杂度有点高,希望能过吧,看看能不能过,做这种题型还是比较吃力

18 ms
页: [1] 2
查看完整版本: Python:每日一题 377