涉及知识点
最大公约 调和级数 并集查找(并差集)
 质数、最大公约数、菲蜀定理
LeetCode 2709. 最大公约数遍历
给你一个下标从 0 开始的整数数组 nums ,你可以在一些下标之间遍历。对于两个下标 i 和 j(i != j),当且仅当 gcd(nums[i], nums[j]) > 1 时,我们可以在两个下标之间通行,其中 gcd 是两个数的 最大公约数 。
 你需要判断 nums 数组中 任意 两个满足 i < j 的下标 i 和 j ,是否存在若干次通行可以从 i 遍历到 j 。
 如果任意满足条件的下标对都可以遍历,那么返回 true ,否则返回 false 。
 示例 1:
 输入:nums = [2,3,6]
 输出:true
 解释:这个例子中,总共有 3 个下标对:(0, 1) ,(0, 2) 和 (1, 2) 。
 从下标 0 到下标 1 ,我们可以遍历 0 -> 2 -> 1 ,我们可以从下标 0 到 2 是因为 gcd(nums[0], nums[2]) = gcd(2, 6) = 2 > 1 ,从下标 2 到 1 是因为 gcd(nums[2], nums[1]) = gcd(6, 3) = 3 > 1 。
 从下标 0 到下标 2 ,我们可以直接遍历,因为 gcd(nums[0], nums[2]) = gcd(2, 6) = 2 > 1 。同理,我们也可以从下标 1 到 2 因为 gcd(nums[1], nums[2]) = gcd(3, 6) = 3 > 1 。
 示例 2:
 输入:nums = [3,9,5]
 输出:false
 解释:我们没法从下标 0 到 2 ,所以返回 false 。
 示例 3:
 输入:nums = [4,3,12,8]
 输出:true
 解释:总共有 6 个下标对:(0, 1) ,(0, 2) ,(0, 3) ,(1, 2) ,(1, 3) 和 (2, 3) 。所有下标对之间都存在可行的遍历,所以返回 true 。
提示:
 1 <= nums.length <= 105
 1 <= nums[i] <= 105
并集查找
每个数的下标都看成一个节点,最大公约数大于1,则相连。本题  
     
      
      
           
       
         ⟺ 
          
       
      
        \iff 
       
      
    ⟺ 只有一个连通区域。
 枚举x 
     
      
       
       
         ∈ 
        
       
      
        \in 
       
      
    ∈ [2,m] ,如果nums[i]是x的倍数,则和前一个x的倍数相连。时间复杂度:O(nlogn),就是n 
     
      
       
       
         × 
        
       
      
        \times 
       
      
    ×调和级数之和。
一,将相同的值x连接起来,x>1。
 二,将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:
	bool canTraverseAllPairs(vector<int>& nums) {
		m_c = nums.size();
		const int iMax = *std::max_element(nums.begin(), nums.end());
		vector<vector<int>> vIndexs(iMax + 1);
		CUnionFind uf(m_c);
		for (int i = 0; i <m_c; i++) {
			auto& v = vIndexs[nums[i]];
			if (v.size()&&(1 != nums[i])) { uf.Union(v.back(), i); }
			v.emplace_back(i);
		}
		for (int x = 2; x <= iMax; x++) {
			int iPre = -1;
			for (int y = x; y <= iMax; y += x) {
				if (vIndexs[y].empty()) { continue; }
				if (-1 != iPre) { uf.Union(iPre, vIndexs[y].back()); }
				iPre = vIndexs[y].back();
			}
		}
		return uf.GetConnetRegionCount() == 1;
	}
	int m_c;
};
测试用例
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]);
	}
}
template<class T>
void Assert(const T& t1, const T& t2)
{
	assert(t1 == t2);
}
int main()
{
	vector<int> nums;
	{
		Solution slu;
		nums = { 1,1 };
		auto res = slu.canTraverseAllPairs(nums);
		Assert(false, res);
	}
	{
		Solution slu;
		nums = { 2, 3, 6 };
		auto res = slu.canTraverseAllPairs(nums);
		Assert(true, res);
	}
	{
		Solution slu;
		nums = { 3,9,5 };
		auto res = slu.canTraverseAllPairs(nums);
		Assert(false, res);
	}
	{
		Solution slu;
		nums = { 4,3,12,8 };
		auto res = slu.canTraverseAllPairs(nums);
		Assert(true, 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++**实现。


















![[C++核心编程-01]----C++内存四区详细解析](https://img-blog.csdnimg.cn/direct/fa47ee2ea80f4fda9b6c27152cef7d04.png)

