【并集查找 最大公约数 调和数】952. 按公因数计算最大组件大小

本文介绍了如何利用图论中的并查集数据结构(CUnionFind类),结合调和数计算方法,解决LeetCode题目952,即在给定数组中找出具有相同公因数的元素构成的最大连通组件的大小。作者提供了C++代码实现和测试用例,展示了如何通过遍历和合并节点来求解这个问题,时间复杂度为O(mlogm)。
摘要由CSDN通过智能技术生成

本文涉及知识点

图论 并集查找 最大公约数 调和数

LeetCode952. 按公因数计算最大组件大小

给定一个由不同正整数的组成的非空数组 nums ,考虑下面的图:
有 nums.length 个节点,按从 nums[0] 到 nums[nums.length - 1] 标记;
只有当 nums[i] 和 nums[j] 共用一个大于 1 的公因数时,nums[i] 和 nums[j]之间才有一条边。
返回 图中最大连通组件的大小 。
示例 1:
在这里插入图片描述
输入:nums = [4,6,15,35]
输出:4
示例 2:
在这里插入图片描述
输入:nums = [20,50,9,63]
输出:2
示例 3:
在这里插入图片描述

输入:nums = [2,3,6,7,4,12,21,39]
输出:8
提示:
1 <= nums.length <= 2 * 104
1 <= nums[i] <= 105
nums 中所有值都 不同

调和数

m = max(nums[i])。
vIndex记录各数的下标:-1,非法。相同的值如果有多个,只记录第一个。重复出现的数和第一个元素连接。
枚举x$\in[1,max(nums[i])] v[x] 记录x的倍数下标。
v[x]的数据分别和v[x][0]连接。
枚举1的倍数,需要运算m次。
枚举2的倍数,需要运算m/2。
枚举3的倍数,需要运算m/3。
⋯ \cdots
总次数为:m(1+1/2+1/3 + ⋯ \cdots +1/m) ,括号内是调和数, ≈ \approx logm。
故总时间复杂度为:O(mlogm)。
v[x] 就是可以只记录一个元素,后面的元素直接和它连接。

代码

核心代码

class CUnionFind
{
public:
	CUnionFind(int iSize) :m_vNodeToRegion(iSize)
	{
		for (int i = 0; i < iSize; i++)
		{
			m_vNodeToRegion[i] = i;
		}
		m_iConnetRegionCount = iSize;
	}	
	CUnionFind(vector<vector<int>>& vNeiBo):CUnionFind(vNeiBo.size())
	{
		for (int i = 0; i < vNeiBo.size(); i++) {
			for (const auto& n : vNeiBo[i]) {
				Union(i, n);
			}
		}
	}
	int GetConnectRegionIndex(int iNode)
	{
		int& iConnectNO = m_vNodeToRegion[iNode];
		if (iNode == iConnectNO)
		{
			return iNode;
		}
		return iConnectNO = GetConnectRegionIndex(iConnectNO);
	}
	void Union(int iNode1, int iNode2)
	{
		const int iConnectNO1 = GetConnectRegionIndex(iNode1);
		const int iConnectNO2 = GetConnectRegionIndex(iNode2);
		if (iConnectNO1 == iConnectNO2)
		{
			return;
		}
		m_iConnetRegionCount--;
		if (iConnectNO1 > iConnectNO2)
		{
			UnionConnect(iConnectNO1, iConnectNO2);
		}
		else
		{
			UnionConnect(iConnectNO2, iConnectNO1);
		}
	}

	bool IsConnect(int iNode1, int iNode2)
	{
		return GetConnectRegionIndex(iNode1) == GetConnectRegionIndex(iNode2);
	}
	int GetConnetRegionCount()const
	{
		return m_iConnetRegionCount;
	}
	vector<int> GetNodeCountOfRegion()//各联通区域的节点数量
	{
		const int iNodeSize = m_vNodeToRegion.size();
		vector<int> vRet(iNodeSize);
		for (int i = 0; i < iNodeSize; i++)
		{
			vRet[GetConnectRegionIndex(i)]++;
		}
		return vRet;
	}
	std::unordered_map<int, vector<int>> GetNodeOfRegion()
	{
		std::unordered_map<int, vector<int>> ret;
		const int iNodeSize = m_vNodeToRegion.size();
		for (int i = 0; i < iNodeSize; i++)
		{
			ret[GetConnectRegionIndex(i)].emplace_back(i);
		}
		return ret;
	}
private:
	void UnionConnect(int iFrom, int iTo)
	{
		m_vNodeToRegion[iFrom] = iTo;
	}
	vector<int> m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引
	int m_iConnetRegionCount;
};

class Solution {
public:
	int largestComponentSize(vector<int>& nums) {
		m_c = nums.size();
		const int iMax = *std::max_element(nums.begin(), nums.end());
		CUnionFind uf(m_c);
		vector<int> vIndex(iMax + 1, -1);
		for (int i = 0; i < m_c; i++) {
			if (-1 == vIndex[nums[i]]) {
				vIndex[nums[i]] = i;
			}
			else
			{
				uf.Union(i, vIndex[nums[i]]);
			}
		}
		for (int x = 2; x <= iMax; x++) {
			int pre = -1;
			for (int cur = x; cur <= iMax; cur += x) {
				if (-1 == vIndex[cur]) { continue; }
				if (-1 == pre) {
					pre = vIndex[cur];
				}
				else {
					uf.Union(pre, vIndex[cur]);
				}
			}
		}
		auto m = uf.GetNodeOfRegion();
		int iRet = 0;
		for (const auto& [tmp, v] : m) {
			iRet = max(iRet, (int)v.size());
		}
		return iRet;
	}
	int m_c;
};

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{

    assert(t1 == t2);
}

template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
    if (v1.size() != v2.size())
    {
        assert(false);
        return;
    }
    for (int i = 0; i < v1.size(); i++)
    {
        Assert(v1[i], v2[i]);
    }

}

int main()
{
	vector<int> nums;
	{
		Solution sln;
		nums = { 20,50,9,63 };
		auto res = sln.largestComponentSize(nums);
		Assert(2, res);
	}
	{
		Solution sln;
		nums = { 4, 6, 15, 35 };
		auto res = sln.largestComponentSize(nums);
		Assert(4, res);
	}
	
	{
		Solution sln;
		nums = { 2,3,6,7,4,12,21,39 };
		auto res = sln.largestComponentSize(nums);
		Assert(8, res);
	}

}

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

  • 39
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闻缺陷则喜何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值