2249. Count Lattice Points Inside a Circle

MediumGeometryArrayHash TableMathEnumeration
Leetcode Link

Problem Description

The problem presents a scenario where multiple circles are drawn on a grid. Each circle is defined by its center point coordinates ( (x_i, y_i) ) and its radius ( r_i ). The task is to determine how many lattice points exist that fall inside at least one of these circles. A lattice point is a point on the grid with integer coordinates, and points lying exactly on the circumference of a circle are counted as inside that circle.

Intuition

To solve this problem, one straightforward approach is to check every possible point in the grid and determine whether it falls inside any of the circles. We do this by:

  1. Identifying the largest possible x and y coordinates that we might need to check. This is determined by finding the maximum x plus radius and the maximum y plus radius from all the circles since a circle could potentially cover points to the right and above its center up to its radius.

  2. We iterate through each point in this bounded grid space.

  3. For each point, we calculate its distance from the center of every given circle using the distance formula ( (dx * dx + dy * dy) ). Here ( dx ) and ( dy ) are the differences in the x and y coordinates of the point and the circle's center, respectively. We compare this distance squared to the radius squared of the circle (since ( \sqrt{dx^2 + dy^2} <= r ) is equivalent to ( dx^2 + dy^2 <= r^2 )). This squared comparison avoids the use of a square root which is more computationally expensive.

  4. If a point is inside any circle (including on the edge), we count it exactly once and then move on to the next point. If a point is not inside the current circle, we continue to check the next circle. Once a point is found inside a circle, there is no need to check the remaining circles which optimizes the process slightly.

  5. Continue this process for all points, and then the ans variable will contain the total count of lattice points inside at least one circle.

Learn more about Math patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

How does quick sort divide the problem into subproblems?

Solution Approach

The implementation uses a brute force algorithm to solve the problem. Given the simplicity of checking whether a point is within a circle, there's no need for complex data structures or advanced patterns. The key steps are as follows:

  1. First, we identify the maximum x-coordinate value (mx) and y-coordinate value (my) that we might need to consider, which will be the bounds for our search space. This is accomplished using list comprehensions that iterate over all the circles and calculate the maximum x-coordinate plus the radius, and the maximum y-coordinate plus the radius, respectively.

  2. Then, we use two nested loops to iterate over every possible lattice point within these bounds. The outer loop runs across the x-coordinate range (from 0 to mx), and the inner loop runs across the y-coordinate range (from 0 to my).

  3. For each point (i, j) in our nested loops, the algorithm then iterates over every circle to check if the point lies within it. This is where the distance formula comes in. It computes the square of the distance from the point to the circle's center, given by the expression dx * dx + dy * dy where dx = i - x and dy = j - y, and compares this with the square of the radius of the circle r * r.

  4. If at any point dx * dx + dy * dy <= r * r evaluates to true, we increment the count (stored in variable ans) by one and break out of the loop that iterates over the circles using break. This ensures we avoid double counting points that lie within multiple circles.

  5. Finally, after all points have been checked, the algorithm returns ans, which contains the total number of lattice points inside at least one circle.

This approach does not use any specific data structure optimizations; it's a direct application of mathematical principles combined with iterative brute force checking. The efficiency could potentially be improved by reducing the search space or by using a geometric data structure to minimize the number of distance checks required, but the given solution is straightforward and relies on the computation being inexpensive enough to check all points within reasonable bounds.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Example Walkthrough

Let's consider a small example with two circles to demonstrate how the solution works:

  1. Suppose we have two circles, ( C_1 ) with center at ( (x_1, y_1) = (2, 3) ) and radius ( r_1 = 2 ), and ( C_2 ) with center at ( (x_2, y_2) = (5, 5) ) and radius ( r_2 = 1 ).

  2. Based on the circles' centers and radii, the maximum x-coordinates we'll need to check is ( x_1 + r_1 = 2 + 2 = 4 ) and ( x_2 + r_2 = 5 + 1 = 6 ). Therefore, the maximum x-coordinate (mx) to consider is 6. Similarly, for the y-coordinates, we have ( y_1 + r_1 = 3 + 2 = 5 ) and ( y_2 + r_2 = 5 + 1 = 6 ). Hence, the maximum y-coordinate (my) to check is 6.

  3. Now, we'll iterate through all lattice points within the bounded space. We start with point (0,0) and end at point (6,6). For illustration, let's check some points to see if they fall inside any circle:

    • Take the point (2,2). For ( C_1 ), we calculate ( dx = 2 - 2 = 0 ) and ( dy = 2 - 3 = -1 ). The distance squared is ( dx^2 + dy^2 = 0^2 + (-1)^2 = 1 ), which is less than ( r_1^2 = 4 ), so the point is inside ( C_1 ). We count it and move to the next point.

    • Consider the point (5,4). For ( C_1 ), ( dx = 5 - 2 = 3 ) and ( dy = 4 - 3 = 1 ) giving us ( dx^2 + dy^2 = 3^2 + 1^2 = 10 ), which is greater than ( r_1^2 = 4 ), so it's outside ( C_1 ). For ( C_2 ), ( dx = 5 - 5 = 0 ) and ( dy = 4 - 5 = -1 ), yielding ( dx^2 + dy^2 = 0^2 + (-1)^2 = 1 ), which is less than ( r_2^2 = 1 ), making the point inside ( C_2 ). We count it.

  4. We continue this process, incrementing our count each time we find a point within any circle.

  5. After completing the iteration over all points, let’s say our counter ans stands at 20. This means there are 20 lattice points that lie within at least one of the circles provided.

This example takes only a small portion of the grid and a couple of circles to illustrate the solution steps in an easily understandable manner. In practice, the iteration would cover a larger set of points and potentially more circles, but the principle remains exactly the same.

Solution Implementation

1from typing import List
2
3class Solution:
4    def countLatticePoints(self, circles: List[List[int]]) -> int:
5        # Initialize a counter for lattice points
6        count = 0
7      
8        # Calculate the maximum x and y coordinates to consider, so the iteration does not go beyond necessary
9        max_x = max(x + r for x, _, r in circles)
10        max_y = max(y + r for _, y, r in circles)
11      
12        # Iterate over all the points within the area defined by max_x and max_y
13        for i in range(max_x + 1):
14            for j in range(max_y + 1):
15                # Check if the current point (i, j) lies within any of the given circles
16                for x, y, radius in circles:
17                    # Calculate the squared distance from the center of the circle (x, y) to the point (i, j)
18                    dx, dy = i - x, j - y
19                    if dx * dx + dy * dy <= radius * radius:
20                        # If the point is within a circle, increment the counter and move to the next point
21                        count += 1
22                        break  # Important to avoid counting a point multiple times
23        # Return the total count of lattice points within all circles
24        return count
25
1class Solution {
2  
3    // This method counts all the lattice points that are inside or on the perimeter of the given circles.
4    // Each circle is represented by an array with three integers: [x_center, y_center, radius].
5    public int countLatticePoints(int[][] circles) {
6        // 'maxX' and 'maxY' are used to determine the size of the grid to check for lattice points.
7        int maxX = 0, maxY = 0;
8      
9        // Calculate the furthest x and y coordinates that we need to check, by looking at the circle
10        // with the largest x + radius and y + radius.
11        for (var circle : circles) {
12            maxX = Math.max(maxX, circle[0] + circle[2]);
13            maxY = Math.max(maxY, circle[1] + circle[2]);
14        }
15      
16        // 'count' will store the total number of lattice points.
17        int count = 0;
18      
19        // Check each point in the grid.
20        for (int x = 0; x <= maxX; ++x) {
21            for (int y = 0; y <= maxY; ++y) {
22                // Check if this point is within or on any of the circles.
23                for (var circle : circles) {
24                    int deltaX = x - circle[0];
25                    int deltaY = y - circle[1];
26                    // Check if the point (x, y) is inside the circle by comparing the square of the distance
27                    // from (x, y) to the circle's center with the square of the circle's radius.
28                    if (deltaX * deltaX + deltaY * deltaY <= circle[2] * circle[2]) {
29                        // If the point is in the circle, increment the count and move to the next point.
30                        ++count;
31                        break; // Move to the next point since we only need to count each point once.
32                    }
33                }
34            }
35        }
36      
37        // Return the total count of lattice points.
38        return count;
39    }
40}
41
1#include <vector>
2#include <algorithm>
3
4class Solution {
5public:
6    int countLatticePoints(vector<vector<int>>& circles) {
7        int maxX = 0, maxY = 0;
8
9        // Find the maximum x and y values to limit the search space
10        for (auto& circle : circles) {
11            maxX = max(maxX, circle[0] + circle[2]);
12            maxY = max(maxY, circle[1] + circle[2]);
13        }
14      
15        int answer = 0; // Initialize count of lattice points
16
17        // Iterate over the bounded search area
18        for (int x = 0; x <= maxX; ++x) {
19            for (int y = 0; y <= maxY; ++y) {
20                // Check every circle to see if the point is inside it
21                for (auto& circle : circles) {
22                    int deltaX = x - circle[0]; // Difference in x
23                    int deltaY = y - circle[1]; // Difference in y
24
25                    // Check if the point (x, y) is within the current circle
26                    if (deltaX * deltaX + deltaY * deltaY <= circle[2] * circle[2]) {
27                        ++answer; // Increment the count if inside any circle
28                        break; // Only count once, even if within multiple circles
29                    }
30                }
31            }
32        }
33
34        return answer; // Return the total count of lattice points
35    }
36};
37
1function countLatticePoints(circles: number[][]): number {
2    // Initialize maximum x and y values to track the furthest points
3    let maxX = 0;
4    let maxY = 0;
5
6    // Determine the maximum x and y values considering all circles
7    for (const [cx, cy, radius] of circles) {
8        maxX = Math.max(maxX, cx + radius);
9        maxY = Math.max(maxY, cy + radius);
10    }
11
12    // Initialize the answer variable to count lattice points
13    let latticePointCount = 0;
14
15    // Iterate over all possible lattice points within the calculated bounds
16    for (let x = 0; x <= maxX; ++x) {
17        for (let y = 0; y <= maxY; ++y) {
18            // Check if current point (x, y) lies within any of the circles
19            for (const [cx, cy, radius] of circles) {
20                const deltaX = x - cx;
21                const deltaY = y - cy;
22                // If point is within the circle, increment the count and break out of the loop
23                if (deltaX * deltaX + deltaY * deltaY <= radius * radius) {
24                    latticePointCount++;
25                    break;
26                }
27            }
28        }
29    }
30
31    // Return the total count of lattice points within all circles
32    return latticePointCount;
33}
34
Not Sure What to Study? Take the 2-min Quiz:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?

Time and Space Complexity

The given code snippet is designed to count all the lattice points (points with integer coordinates) that lie within or on the boundary of given circles. Each circle is represented by its center coordinates and its radius. The code does the following:

  1. Calculates the maximum x and y coordinates (mx and my) that need to be checked based on the sum of the x or y coordinate of a circle's center and its radius. This determines the bounding box within which lattice points could potentially lie inside a circle.
  2. It then iterates over all integer points within this bounding box using two nested loops. For each point (i, j), it checks if the point lies inside any of the circles by calculating the distance from the point to the center of each circle and comparing it with the radius of the circle.
  3. If the point is inside a circle, the count (ans) is incremented by 1, and the innermost loop is broken to avoid counting a point more than once if it lies within multiple circles.

Time Complexity:

The time complexity is determined by the number of iterations in the nested loops.

  • The outer loop runs mx + 1 times and the second loop runs my + 1 times, giving (mx + 1) * (my + 1) for the two combined.
  • The innermost loop runs for each circle, so if there are n circles, it runs n times for each point.

Therefore, the time complexity is O(n * (mx + 1) * (my + 1)), where n is the number of circles, mx is the maximum x-coordinate to be considered, and my is the maximum y-coordinate to be considered.

Space Complexity:

The space complexity of the code is O(1). The only extra space used is a handful of variables for keeping track of counts and iterating through loops, such as ans, mx, my, dx, dy. These require a fixed amount of space that doesn't change with the input size (number of circles, size of coordinates, or radii of the circles).

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫