程序员面试金典 - 面试题 16.13. 平分正方形

题目难度: 中等

原题链接

今天继续更新程序员面试金典系列, 大家在公众号 算法精选 里回复 面试金典 就能看到该系列当前连载的所有文章了, 记得关注哦~

题目描述

给定两个正方形及一个二维平面。请找出将这两个正方形分割成两半的一条直线。假设正方形顶边和底边与 x 轴平行。

每个正方形的数据 square 包含 3 个数值,正方形的左下顶点坐标[X,Y] = [square[0],square[1]],以及正方形的边长 square[2]。所求直线穿过两个正方形会形成 4 个交点,请返回 4 个交点形成线段的两端点坐标(两个端点即为 4 个交点中距离最远的 2 个点,这 2 个点所连成的线段一定会穿过另外 2 个交点)。2 个端点坐标[X1,Y1][X2,Y2]的返回格式为{X1,Y1,X2,Y2},要求若 X1 != X2,需保证 X1 < X2,否则需保证 Y1 <= Y2。

若同时有多条直线满足要求,则选择斜率最大的一条计算并返回(与 Y 轴平行的直线视为斜率无穷大)。

示例:

  • 输入:
    • square1 = {-1, -1, 2}
    • square2 = {0, -1, 2}
  • 输出: {-1,0,2,0}
  • 解释: 直线 y = 0 能将两个正方形同时分为等面积的两部分,返回的两线段端点为[-1,0][2,0]

提示:

  • square.length == 3
  • square[2] > 0

题目思考

  1. 什么直线可以平分两个正方形?
  2. 如何求直线和正方形的交点?

解决方案

思路
  • 根据初中几何知识, 一条直线要想平分一个正方形, 那么其必须经过该正方形的中心
  • 那么要想平分两个正方形, 则该直线一定要同时经过这两个正方形的中心
  • 如果两个中心是同一点, 则根据题目要求, 对应的直线需要是平行 y 轴的; 否则两个点唯一确定一条直线
  • 我们可以根据这两点, 求得直线对应的斜截式 y=kx+b, 注意这里需要先处理平行于坐标轴的两种特殊情况
  • 然后就是求直线和两个正方形的每条边的交点了, 这里将边的固定坐标代入斜截式中, 计算交点的另一坐标, 并判断其是否在当前边内, 在的话则说明直线与该边有交点
  • 最后将所有交点按照 x,y 坐标进行排序, 这样开头和结尾的两个点即为所求端点
  • 下面代码就对应了上面的整个过程, 且每一步有详细的注释, 方便大家理解
复杂度
  • 时间复杂度 O(1): 只进行了几次常数时间的计算
  • 空间复杂度 O(1): 只使用了几个常数空间的变量
代码
class Solution:
    def cutSquares(self, square1: List[int], square2: List[int]) -> List[float]:
        # 中心连线+三情况+斜截式求交点并排序
        # 两个正方形的中心形成的直线即为所求
        x1, y1, e1 = square1
        x2, y2, e2 = square2
        # 计算正方形的中心
        xc1, yc1 = x1 + e1 / 2, y1 + e1 / 2
        xc2, yc2 = x2 + e2 / 2, y2 + e2 / 2
        if xc1 == xc2:
            # 情况1: 对应直线平行于y轴, 所求交点就在最下面和最上面的两个边
            return [xc1, min(y1, y2), xc1, max(y1 + e1, y2 + e2)]
        if yc1 == yc2:
            # 情况2: 对应直线平行于x轴, 所求交点就在最左面和最右面的两个边
            return [min(x1, x2), yc1, max(x1 + e1, x2 + e2), yc2]

        # 情况3: 对应直线不平行于坐标轴, 求出其斜率k和截距b
        k = (yc2 - yc1) / (xc2 - xc1)
        b = yc1 - k * xc1

        def getJoints(square):
            # 计算当前直线y=kx+b与正方形square的交点
            x, y, e = square
            res = []
            for xx in (x, x + e):
                # 先得到垂直边的交点
                yy = k * xx + b
                if y <= yy <= y + e:
                    # 注意计算出来的y值需要在边内部
                    res.append((xx, yy))
            for yy in (y, y + e):
                # 再得到水平边的交点
                xx = (yy - b) / k
                if x <= xx <= x + e:
                    # 注意计算出来的x值需要在边内部
                    res.append((xx, yy))
            return res

        # 求出直线与两个正方形的所有边的交点, 并排序
        joints = getJoints(square1) + getJoints(square2)
        joints.sort()
        # 这样开头和结尾的两个点即为端点
        return [joints[0][0], joints[0][1], joints[-1][0], joints[-1][1]]

大家可以在下面这些地方找到我~😊

我的 GitHub

我的 Leetcode

我的 CSDN

我的知乎专栏

我的头条号

我的牛客网博客

我的公众号: 算法精选, 欢迎大家扫码关注~😊

算法精选 - 微信扫一扫关注我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值