一、国际象棋跳跃问题
问题理解
小U掌握了国际象棋中“象”和“马”的跳跃能力,在一个无限大的平面直角坐标系中,她每一步可以模仿象和马的跳跃方式移动。在每次询问中,小U需要计算从初始坐标 (x1, y1) 到目标坐标 (x2, y2) 所需的最少步数。
- 象的跳跃:可以移动到
(x + k, y + k)或(x + k, y - k),其中k是任意整数。 - 马的跳跃:可以移动到
(x + 2, y + 1),(x + 2, y - 1),(x - 2, y + 1),(x - 2, y - 1),(x + 1, y + 2),(x + 1, y - 2),(x - 1, y + 2),(x - 1, y - 2)。
数据结构选择
- 队列:用于广度优先搜索(BFS),记录当前坐标和步数。
- 集合:用于记录已访问的坐标,防止重复访问。
算法步骤
-
初始化:
- 创建一个队列
queue,初始状态为(x1, y1, 0),表示起点坐标和步数为0。 - 创建一个集合
visited,记录已访问的坐标,初始状态为{(x1, y1)}。
- 创建一个队列
-
广度优先搜索(BFS) :
-
当队列不为空时,执行以下操作:
-
从队列中取出当前坐标
(x, y)和步数steps。 -
处理马的跳跃:
- 对于每个马的跳跃方向
(dx, dy),计算新坐标(nx, ny)。 - 如果新坐标等于目标坐标
(x2, y2),返回steps + 1。 - 如果新坐标未被访问过,加入队列并标记为已访问。
- 对于每个马的跳跃方向
-
处理象的跳跃:
- 对于每个可能的
k值,计算新坐标(nx1, ny1)和(nx2, ny2)。 - 如果新坐标等于目标坐标
(x2, y2),返回steps + 1。 - 如果新坐标未被访问过,加入队列并标记为已访问。
- 对于每个可能的
-
-
-
返回结果:
- 如果队列为空且未找到目标坐标,返回
-1(理论上不会到达这里,因为题目保证会有解)。
- 如果队列为空且未找到目标坐标,返回
代码实现
from collections import deque
def solution(x1, y1, x2, y2):
# 马的跳跃方向
knight_moves = [(2, 1), (2, -1), (-2, 1), (-2, -1), (1, 2), (1, -2), (-1, 2), (-1, -2)]
# 初始化队列和已访问集合
queue = deque([(x1, y1, 0)])
visited = set()
visited.add((x1, y1))
while queue:
x, y, steps = queue.popleft()
# 先处理马的跳跃
for dx, dy in knight_moves:
nx, ny = x + dx, y + dy
if (nx, ny) == (x2, y2):
return steps + 1
if (nx, ny) not in visited:
visited.add((nx, ny))
queue.append((nx, ny, steps + 1))
# 然后处理象的跳跃
# 只需考虑在当前方向上加减 k
for k in range(-100, 101): # 选择一个合理的范围
# 对角线移动
nx1, ny1 = x + k, y + k
if (nx1, ny1) == (x2, y2):
return steps + 1
if (nx1, ny1) not in visited:
visited.add((nx1, ny1))
queue.append((nx1, ny1, steps + 1))
nx2, ny2 = x + k, y - k
if (nx2, ny2) == (x2, y2):
return steps + 1
if (nx2, ny2) not in visited:
visited.add((nx2, ny2))
queue.append((nx2, ny2, steps + 1))
return -1 # 理论上不会到达这里,因为题目保证会有解
二、小A的移动点
问题理解
小M有 n 个点,每个点的坐标为 (xi, yi)。她可以从一个点出发,平行于坐标轴移动,直到到达另一个点。具体来说,她可以从 (x1, y1) 移动到 (x2, y1) 或者 (x1, y2)。你的任务是计算最少需要增加多少个点,才能保证任意两个点之间可以通过平行于坐标轴的路径互相到达。
数据结构选择
- 并查集(Union-Find) :用于管理和合并连通分量。
- 字典(defaultdict) :用于按
x和y轴分组点的索引。
算法步骤
-
初始化并查集:
- 创建一个并查集
parent,初始时每个点的父节点是它自己。 find函数用于查找并返回节点x的根节点,并进行路径压缩。union函数用于合并两个节点x和y所在的连通分量。
- 创建一个并查集
-
按
x和y轴分组:- 使用两个字典
x_groups和y_groups,分别按x和y轴分组点的索引。
- 使用两个字典
-
合并同一组的点:
- 对于每个
x轴分组中的点,使用并查集将它们合并。 - 对于每个
y轴分组中的点,使用并查集将它们合并。
- 对于每个
-
计算连通分量数量:
- 遍历所有点,使用并查集找到每个点的根节点,统计不同根节点的数量。
-
计算最少新增点数:
- 最少新增点数为连通分量数量减
1。
- 最少新增点数为连通分量数量减
代码实现
from collections import defaultdict
def solution(n: int, points: list) -> int:
# 初始化并查集
parent = list(range(n))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
rootX = find(x)
rootY = find(y)
if rootX != rootY:
parent[rootY] = rootX
# 按 x 和 y 轴分组
x_groups = defaultdict(list)
y_groups = defaultdict(list)
for i, (x, y) in enumerate(points):
x_groups[x].append(i)
y_groups[y].append(i)
# 合并同一组的点
for indices in x_groups.values():
for i in range(1, len(indices)):
union(indices[0], indices[i])
for indices in y_groups.values():
for i in range(1, len(indices)):
union(indices[0], indices[i])
# 计算连通分量数量
unique_roots = len(set(find(i) for i in range(n)))
# 最少新增点数为连通分量数量减 1
return unique_roots - 1