2026-03-05:最大划分因子。用go语言,给定一个二维整数数组 points,其中每个元素 points[i] = [xi, yi] 表示平面上的一个点。

0 阅读9分钟

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:预处理所有点对的曼哈顿距离

  1. 遍历所有点对(i,j)(i>j,避免重复计算),计算每对点的曼哈顿距离|xi-xj| + |yi-yj|
  2. 将所有点对存储为三元组(距离, 点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:按距离从小到大合并点对,检测矛盾

算法的核心逻辑是:尝试让距离小的点对属于不同集合,直到出现矛盾——此时的距离就是最大划分因子。具体过程:

  1. 遍历排序后的点对(从距离最小到最大):
    • 对当前点对(x,y),调用find函数找到x和y的代表元(同时做路径压缩,更新dis标记);
    • 如果x和y的代表元相同:说明x和y必须在同一集合,但当前合并逻辑要求它们在不同集合(矛盾),此时返回当前点对的距离(这就是最大划分因子);
    • 如果代表元不同:执行merge操作,将x的连通分量合并到y的连通分量,并更新dis标记(保证x和y属于不同集合)。
  2. 以输入用例为例:
    • 先处理距离为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”的约定)。

三、核心概念补充(并查集的findmerge逻辑)

  • 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²)次),每次findmerge操作的均摊时间是O(α(n))(α是阿克曼函数的反函数,增长极慢,可视为常数);
  • 总时间复杂度:O(n² logn)(排序是主导项)。

2. 额外空间复杂度

  • 存储所有点对的三元组:O(n²)(最多n*(n-1)/2个点对);
  • 并查集的fadis数组:O(n)
  • 总额外空间复杂度:O(n²)(点对存储是主导项)。

五、总结

  1. 核心思路:反向贪心+带权并查集,通过“从小到大合并点对,检测集合划分矛盾”找到最大划分因子;
  2. 关键操作:并查集的路径压缩(find)和带权合并(merge),保证集合划分的一致性检测;
  3. 复杂度:时间复杂度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;
}

在这里插入图片描述