玩具套圈

标签: 几何 数组 哈希表 数学 二分查找 排序

难度: Hard

「力扣挑战赛」场地外,小力组织了一个套玩具的游戏。所有的玩具摆在平地上,`toys[i]` 以 `[xi,yi,ri]` 的形式记录了第 `i` 个玩具的坐标 `(xi,yi)` 和半径 `ri`。小扣试玩了一下,他扔了若干个半径均为 `r` 的圈,`circles[j]` 记录了第 `j` 个圈的坐标 `(xj,yj)`。套圈的规则如下: - 若一个玩具被某个圈完整覆盖了(即玩具的任意部分均在圈内或者圈上),则该玩具被套中。 - 若一个玩具被多个圈同时套中,最终仅计算为套中一个玩具 请帮助小扣计算,他成功套中了多少玩具。 **注意:** - 输入数据保证任意两个玩具的圆心不会重合,但玩具之间可能存在重叠。 **示例 1:** > 输入:`toys = [[3,3,1],[3,2,1]], circles = [[4,3]], r = 2` > > 输出:`1` > > 解释: 如图所示,仅套中一个玩具 ![image.png](https://pic.leetcode-cn.com/1629194140-ydKiGF-image.png) **示例 2:** > 输入:`toys = [[1,3,2],[4,3,1],[7,1,2]], circles = [[1,0],[3,3]], r = 4` > > 输出:`2` > > 解释: 如图所示,套中两个玩具 ![image.png](https://pic.leetcode-cn.com/1629194157-RiOAuy-image.png){:width="400px"} **提示:** - `1 <= toys.length <= 10^4` - `0 <= toys[i][0], toys[i][1] <= 10^9` - `1 <= circles.length <= 10^4` - `0 <= circles[i][0], circles[i][1] <= 10^9` - `1 <= toys[i][2], r <= 10`

Submission

运行时间: 288 ms

内存: 36.2 MB

"""
iteration + sorting + binary search
T: O(n+Cm)
S: O(1)

if circle j can hoop the toyi,
then toyi is Inclusion, inscribed or totally overlapping with cicle j
xj.e., 0 <= d <= r - ri 

since 1 <= r, ri <= 10
then d <= 10-1=9

since d is integer, then d is in range [0, 9]
we can iterates all possble centers of circle for each toy
"""

class Solution:
    def circleGame(self, toys: List[List[int]], circles: List[List[int]], r: int) -> int:
        def check(xi: int, yi: int, ri: int) -> bool:
            """
            whether ith toy can be hooped by any circle
            """
            d = r-ri
            if d < 0:
                return False 
            
            left, right = xi-d, xi+d
            down, up = yi-d, yi+d

            for xj in range(left, right+1):
                if not yjs[xj]:
                    continue 

                a = bisect_left(yjs[xj], down)
                b = bisect_right(yjs[xj], up)

                for idx in range(a, b):
                    yj = yjs[xj][idx]

                    if (xj-xi)**2 + (yj-yi)**2 <= d**2:
                        return True 
            
            return False 

        m, n = len(toys), len(circles)

        # 1. O(nlogn) sort circles array by xj and yj
        circles.sort()

        # 2. O(n) store yjs of each xj in circles array
        yjs = defaultdict(list)
        for xj, yj in circles:
            yjs[xj].append(yj)
        
        # 3. O(m) iterates over toys array  
        ans = sum(check(*toy) for toy in toys)

        return ans 

Explain

此题解采用迭代+排序+二分搜索的方法来确定玩具是否被套圈。首先,对所有的圈按照x坐标进行排序,并为每个x坐标建立一个列表,存储对应的y坐标。对于每个玩具,根据其位置和大小计算出可能套中该玩具的圈的潜在x和y坐标范围。接着,对每个玩具遍历这个范围内的所有潜在圈的坐标,使用二分搜索确定潜在的y坐标。若计算得出的圈心到玩具心的距离小于或等于圈半径减去玩具半径的平方,则该玩具被套中。

时间复杂度: O(n + Cm)

空间复杂度: O(n)

class Solution:
    def circleGame(self, toys: List[List[int]], circles: List[List[int]], r: int) -> int:
        def check(xi: int, yi: int, ri: int) -> bool:
            # 检查是否可以将第i个玩具套圈
            d = r-ri
            if d < 0:
                return False
            left, right = xi-d, xi+d
            down, up = yi-d, yi+d
            for xj in range(left, right+1):
                if not yjs[xj]:
                    continue
                a = bisect_left(yjs[xj], down)
                b = bisect_right(yjs[xj], up)
                for idx in range(a, b):
                    yj = yjs[xj][idx]
                    if (xj-xi)**2 + (yj-yi)**2 <= d**2:
                        return True
            return False
        m, n = len(toys), len(circles)
        circles.sort() # 按x坐标排序圈的数组
        yjs = defaultdict(list) # 为每个x坐标存储y坐标的列表
        for xj, yj in circles:
            yjs[xj].append(yj)
        ans = sum(check(*toy) for toy in toys) # 对所有玩具应用check函数并计数
        return ans

Explore

在题解中选择对圈的x坐标进行排序是因为这样可以根据玩具的x坐标快速地定位可能与玩具相交的圈的潜在范围。通过排序x坐标,我们可以利用二分搜索快速缩小圈的搜索范围,从而减少不必要的检查。如果同时对x和y坐标排序,则会增加处理排序后数据的复杂性,因为我们需要同时考虑二维上的位置关系,这可能导致效率降低。因此,单独对x坐标进行排序并结合y坐标的列表,是一个在效率和实现复杂度之间较好的折中。

在排序圈的x坐标后,使用字典来存储每个x坐标对应的y坐标列表是为了快速访问和检索特定x坐标下所有可能的y坐标。这种数据结构的优势在于它提供了快速的查找时间和灵活的数据访问。对于每个特定的x坐标,我们可以直接通过字典访问所有相关的y坐标,而不需要遍历整个圈的列表。这样可以显著提高查询效率,尤其是在处理大量数据时。此外,字典的使用也支持动态插入和删除操作,这在处理变动的数据集时非常有用。

是的,这里的逻辑正是意味着只有当圈的半径大于或等于玩具的半径时,玩具才可能被套中。这是因为如果圈的半径小于玩具的半径,即使圈的中心点正好位于玩具的中心点上,圈也无法完全覆盖玩具。因此,只有当圈的半径不小于玩具的半径时,我们才继续计算圈心到玩具心的距离是否小于或等于调整后的圈半径的平方(即 `d^2`),以判断玩具是否被套中。