On a 2D plane, we place n
stones at some integer coordinate points. Each coordinate point may have at most one stone.
A stone can be removed if it shares either the same row or the same column as another stone that has not been removed.
Given an array stones
of length n
where stones[i] = [x<sub>i</sub>, y<sub>i</sub>]
represents the location of the i<sup>th</sup>
stone, return the largest possible number of stones that can be removed.
For example:
stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
. The output should be 5. One way to remove 5 stones is as follows:
Stone [0,0] cannot be removed since it does not share a row/column with another stone still on the plane.
stones = [[0,0],[0,2],[1,1],[2,0],[2,2]]
. The output should be 3. One way to make 3 moves is as follows:
Stones [0,0] and [1,1] cannot be removed since they do not share a row/column with another stone still on the plane.
stones = [[0,0]]
. The output should be 0 because [0,0] is the only stone on the plane, so you cannot remove it.
How would you implement a function to solve this problem, and what is the time and space complexity of your solution?
A straightforward approach would involve iterating through the stones and, for each stone, checking if there's another stone in the same row or column. If found, remove the current stone and repeat the process. This continues until no more stones can be removed.
stones
list to represent the current state of stones on the plane.def remove_stones_brute_force(stones):
n = len(stones)
removed_count = 0
while True:
removed_in_iteration = False
stones_to_remove = []
for i in range(len(stones)):
for j in range(len(stones)):
if i != j and (stones[i][0] == stones[j][0] or stones[i][1] == stones[j][1]):
stones_to_remove.append(stones[i])
removed_count += 1
removed_in_iteration = True
break
if removed_in_iteration:
break
if not removed_in_iteration:
break
stones = [stone for stone in stones if stone not in stones_to_remove]
return removed_count
O(n^3) in the worst case, where n is the number of stones. The nested loops iterate through the stones multiple times.
O(n) to store the copy of the stones list.
The problem can be modeled as a graph where each stone is a node, and an edge exists between two stones if they share the same row or column. The maximum number of removable stones is equal to the total number of stones minus the number of connected components in the graph.
We can use the Disjoint Set Union (DSU) data structure to efficiently find the number of connected components.
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # Path compression
return self.parent[x]
def union(self, x, y):
root_x = self.find(x)
root_y = self.find(y)
if root_x != root_y:
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1
def remove_stones_dsu(stones):
n = len(stones)
dsu = DSU(n)
for i in range(n):
for j in range(i + 1, n):
if stones[i][0] == stones[j][0] or stones[i][1] == stones[j][1]:
dsu.union(i, j)
num_components = 0
for i in range(n):
if dsu.find(i) == i:
num_components += 1
return n - num_components
O(n^2 * α(n)), where n is the number of stones and α(n) is the inverse Ackermann function, which grows very slowly and can be considered almost constant for practical input sizes. Thus, effectively O(n^2).
O(n) to store the parent and rank arrays in the DSU data structure.
stones
list is empty, return 0.