2026-03-05:最大划分因子。用go语言,给定一个二维整数数组 points,其中每个元素 points[i] = [xi, yi] 表示平面上的一个点。
两点之间的曼哈顿距离定义为坐标差的绝对值之和:|xi - xj| + |yi - yj|。
把这些点划分成正好两个互不为空的集合。对于某一次划分,先看每个集合内部所有未排序的点对,计算这些点对的曼哈顿距离,取其中的最小值,这个最小值就是该划分的“划分指标”。要求在所有可能的合法划分中,找出使该指标最大化的划分,并返回这个最大值。
说明:若某个集合只含一个点,则该集合内部没有点对可供计算;当 n = 2 且两个集合各含一个点时,划分指标按约定为 0。
2 <= points.length <= 500。
points[i] = [xi, yi]。
-100000000 <= xi, yi <= 100000000。
输入: points = [[0,0],[0,2],[2,0],[2,2]]。
输出: 4。
解释:
我们将点分成两组: {[0, 0], [2, 2]} 和 {[0, 2], [2, 0]}。
在第一组中,唯一的点对之间的曼哈顿距离是 |0 - 2| + |0 - 2| = 4。
在第二组中,唯一的点对之间的曼哈顿距离也是 |0 - 2| + |2 - 0| = 4。
此划分的划分因子是 min(4, 4) = 4,这是最大值。
题目来自力扣3710。
一、需求理解
你想让我基于提供的Go语言代码,详细拆解最大划分因子问题的解决过程,并分析其时间复杂度和空间复杂度。核心目标是:把平面点划分为两个非空集合,使得两个集合内部点对的曼哈顿距离最小值(划分指标)最大,求这个最大值。
二、解题过程分步解析
整个算法的核心思路是反向贪心 + 并查集(带权):我们希望划分指标尽可能大,等价于“找到最大的D,使得存在一种划分,让同一集合内的任意两点曼哈顿距离≥D,且跨集合的点对中存在距离=D的情况”。具体步骤如下:
步骤1:预处理所有点对的曼哈顿距离
- 遍历所有点对(i,j)(i>j,避免重复计算),计算每对点的曼哈顿距离
|xi-xj| + |yi-yj|。 - 将所有点对存储为三元组
(距离, 点i索引, 点j索引),并按距离从小到大排序。- 例如输入用例
[[0,0],[0,2],[2,0],[2,2]],所有点对的距离为:- (0,0)与(0,2):2;(0,0)与(2,0):2;(0,0)与(2,2):4;
- (0,2)与(2,0):4;(0,2)与(2,2):2;(2,0)与(2,2):2;
- 排序后点对顺序为:距离2的点对先出现,然后是距离4的点对。
- 例如输入用例
步骤2:初始化带权并查集
并查集是解决“动态连通性”问题的数据结构,这里的带权是关键:
fa[x]:表示点x的父节点,用于找连通分量的代表元;dis[x]:表示x到其代表元的“距离标记”(本题中是二进制标记,0/1表示是否在同一集合);- 初始化时,每个点的父节点是自己(
fa[i]=i),到自身的距离标记为0(dis[i]=0)。
步骤3:按距离从小到大合并点对,检测矛盾
算法的核心逻辑是:尝试让距离小的点对属于不同集合,直到出现矛盾——此时的距离就是最大划分因子。具体过程:
- 遍历排序后的点对(从距离最小到最大):
- 对当前点对(x,y),调用
find函数找到x和y的代表元(同时做路径压缩,更新dis标记); - 如果x和y的代表元相同:说明x和y必须在同一集合,但当前合并逻辑要求它们在不同集合(矛盾),此时返回当前点对的距离(这就是最大划分因子);
- 如果代表元不同:执行
merge操作,将x的连通分量合并到y的连通分量,并更新dis标记(保证x和y属于不同集合)。
- 对当前点对(x,y),调用
- 以输入用例为例:
- 先处理距离为2的点对(如(0,0)和(0,2)):合并这两个点,标记为不同集合;
- 继续处理其他距离为2的点对(如(0,0)和(2,0)、(0,2)和(2,2)、(2,0)和(2,2)):依次合并,均无矛盾;
- 处理距离为4的点对(如(0,0)和(2,2)):调用
find后发现,(0,0)和(2,2)的代表元相同,且dis[0] == dis[3](矛盾),因此返回距离4(即答案)。
步骤4:边界情况处理
如果遍历完所有点对都没有矛盾(如n=2时),返回0(符合题目中“两个集合各含一个点时划分指标为0”的约定)。
三、核心概念补充(并查集的find和merge逻辑)
find函数(路径压缩): 递归找到x的代表元,同时将x的父节点直接指向代表元(路径压缩),并更新dis[x]为x到代表元的总距离标记(dis[x] ^= dis[fa[x]],异或操作保证标记的传递性)。merge函数(合并连通分量): 当x和y的代表元不同时,调整x代表元的父节点为y的代表元,并计算dis[x] = 1 ^ dis[to] ^ dis[from](保证from和to属于不同集合,1是“不同集合”的标记)。
四、时间复杂度与空间复杂度分析
1. 时间复杂度
- 步骤1:生成所有点对的时间是
O(n²)(n是点的数量),排序的时间是O(n² log(n²)) = O(n² logn); - 步骤2:初始化并查集的时间是
O(n); - 步骤3:遍历所有点对(
O(n²)次),每次find和merge操作的均摊时间是O(α(n))(α是阿克曼函数的反函数,增长极慢,可视为常数); - 总时间复杂度:
O(n² logn)(排序是主导项)。
2. 额外空间复杂度
- 存储所有点对的三元组:
O(n²)(最多n*(n-1)/2个点对); - 并查集的
fa和dis数组:O(n); - 总额外空间复杂度:
O(n²)(点对存储是主导项)。
五、总结
- 核心思路:反向贪心+带权并查集,通过“从小到大合并点对,检测集合划分矛盾”找到最大划分因子;
- 关键操作:并查集的路径压缩(
find)和带权合并(merge),保证集合划分的一致性检测; - 复杂度:时间复杂度
O(n² logn),空间复杂度O(n²)(n为点的数量)。
Go完整代码如下:
package main
import (
"fmt"
"slices"
)
type unionFind struct {
fa []int
dis []int8 // dis[x] 表示 x 到其代表元的距离
}
func newUnionFind(n int) unionFind {
fa := make([]int, n)
dis := make([]int8, n)
for i := range fa {
fa[i] = i
}
return unionFind{fa, dis}
}
// 返回 x 所在集合的代表元
// 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元
func (u unionFind) find(x int) int {
if u.fa[x] != x {
rt := u.find(u.fa[x])
u.dis[x] ^= u.dis[u.fa[x]] // 更新 x 到其代表元的距离
u.fa[x] = rt
}
return u.fa[x]
}
// 合并两个互斥的点
// 如果已经合并,返回是否与已知条件矛盾
func (u *unionFind) merge(from, to int) bool {
x, y := u.find(from), u.find(to)
if x == y { // from 和 to 在同一个集合,不合并
return u.dis[from] != u.dis[to] // 是否与已知信息矛盾
}
// 2 ------ 4
// / /
// 1 ------ 3
// 如果知道 1->2 的距离和 3->4 的距离,现在合并 1 和 3,并传入 1->3 的距离(本题等于 1)
// 由于 1->3->4 和 1->2->4 的距离相等
// 所以 2->4 的距离为 (1->3) + (3->4) - (1->2)
u.dis[x] = 1 ^ u.dis[to] ^ u.dis[from]
u.fa[x] = y
return true
}
func maxPartitionFactor(points [][]int) int {
n := len(points)
type tuple struct{ dis, x, y int }
manhattanTuples := make([]tuple, 0, n*(n-1)/2) // 预分配空间
for i, p := range points {
for j, q := range points[:i] {
manhattanTuples = append(manhattanTuples, tuple{abs(p[0]-q[0]) + abs(p[1]-q[1]), i, j})
}
}
slices.SortFunc(manhattanTuples, func(a, b tuple) int { return a.dis - b.dis })
uf := newUnionFind(n)
for _, t := range manhattanTuples {
if !uf.merge(t.x, t.y) {
return t.dis // t.x 和 t.y 必须在同一个集合,t.dis 就是这一划分的最小划分因子
}
}
return 0
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
func main() {
points := [][]int{{0, 0}, {0, 2}, {2, 0}, {2, 2}}
result := maxPartitionFactor(points)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
class UnionFind:
def __init__(self, n):
self.fa = list(range(n)) # 父节点数组
self.dis = [0] * n # dis[x] 表示 x 到其代表元的距离
def find(self, x):
"""返回 x 所在集合的代表元,同时做路径压缩"""
if self.fa[x] != x:
rt = self.find(self.fa[x])
self.dis[x] ^= self.dis[self.fa[x]] # 更新 x 到其代表元的距离
self.fa[x] = rt
return self.fa[x]
def merge(self, from_node, to_node):
"""合并两个互斥的点,如果已经合并,返回是否与已知条件矛盾"""
x = self.find(from_node)
y = self.find(to_node)
if x == y: # from_node 和 to_node 在同一个集合,不合并
return self.dis[from_node] != self.dis[to_node] # 是否与已知信息矛盾
# 合并两个集合
self.dis[x] = 1 ^ self.dis[to_node] ^ self.dis[from_node]
self.fa[x] = y
return True
def max_partition_factor(points):
n = len(points)
manhattan_tuples = []
# 计算所有点对之间的曼哈顿距离
for i in range(n):
for j in range(i):
dist = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1])
manhattan_tuples.append((dist, i, j))
# 按距离排序
manhattan_tuples.sort(key=lambda x: x[0])
uf = UnionFind(n)
for dist, i, j in manhattan_tuples:
if not uf.merge(i, j):
return dist # i 和 j 必须在同一个集合,dist 就是这一划分的最小划分因子
return 0
def main():
points = [[0, 0], [0, 2], [2, 0], [2, 2]]
result = max_partition_factor(points)
print(result)
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
class UnionFind {
private:
vector<int> fa; // 父节点数组
vector<char> dis; // dis[x] 表示 x 到其代表元的距离 (使用char代替int8_t)
public:
UnionFind(int n) {
fa.resize(n);
dis.resize(n, 0);
for (int i = 0; i < n; i++) {
fa[i] = i;
}
}
// 返回 x 所在集合的代表元
// 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元
int find(int x) {
if (fa[x] != x) {
int rt = find(fa[x]);
dis[x] ^= dis[fa[x]]; // 更新 x 到其代表元的距离
fa[x] = rt;
}
return fa[x];
}
// 合并两个互斥的点
// 如果已经合并,返回是否与已知条件矛盾
bool merge(int from, int to) {
int x = find(from);
int y = find(to);
if (x == y) { // from 和 to 在同一个集合,不合并
return dis[from] != dis[to]; // 是否与已知信息矛盾
}
// 2 ------ 4
// / /
// 1 ------ 3
// 如果知道 1->2 的距离和 3->4 的距离,现在合并 1 和 3,并传入 1->3 的距离(本题等于 1)
// 由于 1->3->4 和 1->2->4 的距离相等
// 所以 2->4 的距离为 (1->3) + (3->4) - (1->2)
dis[x] = 1 ^ dis[to] ^ dis[from];
fa[x] = y;
return true;
}
};
struct Tuple {
int dis;
int x;
int y;
Tuple(int d, int a, int b) : dis(d), x(a), y(b) {}
};
int maxPartitionFactor(vector<vector<int>>& points) {
int n = points.size();
vector<Tuple> manhattanTuples;
manhattanTuples.reserve(n * (n - 1) / 2); // 预分配空间
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
int dist = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
manhattanTuples.emplace_back(dist, i, j);
}
}
// 按距离排序
sort(manhattanTuples.begin(), manhattanTuples.end(),
[](const Tuple& a, const Tuple& b) { return a.dis < b.dis; });
UnionFind uf(n);
for (const auto& t : manhattanTuples) {
if (!uf.merge(t.x, t.y)) {
return t.dis; // t.x 和 t.y 必须在同一个集合,t.dis 就是这一划分的最小划分因子
}
}
return 0;
}
int main() {
vector<vector<int>> points = {{0, 0}, {0, 2}, {2, 0}, {2, 2}};
int result = maxPartitionFactor(points);
cout << result << endl;
return 0;
}