【力扣分模块练习】并查集及其变型

1202. 交换字符串中的元素

给你一个字符串 s,以及该字符串中的一些「索引对」数组 pairs,其中 pairs[i] = [a, b] 表示字符串中的两个索引(编号从 0 开始)。
你可以 任意多次交换 在 pairs 中任意一对索引处的字符。
返回在经过若干次交换后,s 可以变成的按字典序最小的字符串。

示例 1:
输入:s = “dcab”, pairs = [[0,3],[1,2]]
输出:“bacd”
解释:
交换 s[0] 和 s[3], s = “bcad”
交换 s[1] 和 s[2], s = “bacd”

示例2:
输入:s = “dcab”, pairs = [[0,3],[1,2],[0,2]]
输出:“abcd”
解释:
交换 s[0] 和 s[3], s = “bcad”
交换 s[0] 和 s[2], s = “acbd”
交换 s[1] 和 s[2], s = “abcd”

思路:
重点是理解题意,如示例1和示例2做对比,发现主要出现了[1,2]这种交换方式,使得结果发生了不同。
而题目中说道可以无限交换次数,这样一来,只要两个位置之间间接的存在交换方式,都可以使得最后的结果变成最简的。当然这种最简形式要在可以连通的几个位置。 因此,想到使用判断连通集合的方法–并查集。

判断完集合后,使用一个Map来映射根节点和并查集元素的关系。这里用了个小技巧,用反向排序,来把最小的元素放于vector的最后,这样就可以用pop_back来控制减少。
最后一步,遍历每个位置,用findfather来找出他的根,通过根来找到并查集元素,每次减少一个最小的。

class Solution {
public:
	string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {
		int n = s.length();
		vector<int> father(n);
		for (int i = 0; i < n; ++i)
			father[i] = i;
		
		for (int i = 0; i < pairs.size(); ++i) //连接起来各个点
		{
			Union(pairs[i][0], pairs[i][1], father);
		}

		string ans;
		map<int, vector<int>> mp;
		for (int i = 0; i < n; ++i) //父节点脚标下挂该集合所有元素
			mp[findfather(i, father)].push_back(i);
				
		for (int i = 0; i < n; ++i)
		{		
			if (father[i] == i)  //是根节点
			{
				sort(mp[i].begin(), mp[i].end(), [&](int a,int b) {
					return s[a] > s[b];
				}); //字典序快排	小的再后面,方便存取		
			}
		}
		
		for (int i = 0; i < s.length(); i++)
		{
			int x = findfather(i, father);
			ans += s[mp[x].back()];
			mp[x].pop_back();
		}

		return ans;
	}
private:
	int findfather(int x, vector<int>& father)
	{
		return x == father[x] ? x : father[x] = findfather(father[x],father);
	}
	
	void Union(int a,int b,vector<int>& father)
	{
		int A = findfather(a, father);
		int B = findfather(b, father);
		if (A != B)
			father[B] = A;
	}
};

Kruskal 算法 + 并查集


1584. 连接所有点的最小费用
给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。
连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。
请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。

输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20

思路步骤:
1.先用边信息存到一个容易里面,存好每个点之间的边。
2.然后sort排序,把点之间距离短的边放在前面。
3.最后从前到后遍历,发现如果这条边连接的两个点,之前没有连接,则把这条边加入集合中,并用Union把这两点连起来。

class Solution {
public:
	struct Edge {
		int len,x, y;
		Edge(int len, int x, int y) : len(len), x(x), y(y) {}
	};

	vector<int> father;
	vector<Edge> vec;

	int minCostConnectPoints(vector<vector<int>>& points) {
		int n = points.size();
		father.resize(n);
		for (int i = 0; i < n; i++)  //father初始化
			father[i] = i;

		for (int i = 0; i < n; i++)  //填入边
		{
			for (int j = i; j < n; j++)
			{
				int len = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
				vec.push_back({ len,i,j });
			}
		}
		//从小到大排序
		sort(vec.begin(), vec.end(), [&](Edge A,Edge B) {
			return A.len < B.len;
		});

		int sum = 0;
		for (int i = 0; i < vec.size(); i++)
		{
			Edge e = vec[i];
			if (findfather(e.x) != findfather(e.y))  //如果这条边的两个顶点目前不连通
			{
				sum += e.len; //加入连通集合中
				Union(e.x, e.y);
			}
		}

		return sum;
	}

private:
	int findfather(int x)
	{
		return x == father[x] ? x : father[x] = findfather(father[x]);
	}

	void Union(int a, int b)
	{
		int A = findfather(a);
		int B = findfather(b);
		if (A != B)
			father[B] = A; 
	}
};

1631. 最小体力消耗路径
你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。
一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。
请你返回从左上角走到右下角的最小 体力消耗值 。

样例:
输入:heights = [[1,2,2],[3,8,2],[5,3,5]]
输出:2
解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。

在这里插入图片描述

思路:
一开始想着DP,后面发现方向没法确定。
后来想DFS回溯,感觉会超时,加上这个本题难点–最短路径的定义不是路径长度而是相邻点的路径最大值。
后来喵答案发现Kruskal可以做。结果就很顺了。

要注意填入边值得那里,那段展平的思路,非常妙。

class Solution {
public:
	struct Edge {
		int x, y,len;
		Edge(int x, int y, int len) : x(x), y(y), len(len) {};
	};

	vector<int> father;
	vector<Edge> vec;
	int minimumEffortPath(vector<vector<int>>& heights) {
		int m = heights.size();
		int n = heights[0].size();

		father.resize(m*n);
		for (int i = 0; i < m*n; i++)  //初始化father
			father[i] = i;
		

		for (int i = 0; i < m; i++)
		{
			for (int j = 0; j < n; j++)
			{
				int id = i * n + j; //二维展平
				if (i > 0)
				{
					int len = abs(heights[i][j] - heights[i - 1][j]);
					vec.push_back(Edge(id - n, id, len)); //与上一格的边
				}
				if (j > 0)
				{
					int len = abs(heights[i][j] - heights[i][j - 1]);
					vec.push_back(Edge(id - 1, id, len)); //与左边一格的边
				}
			}
		}
		sort(vec.begin(), vec.end(), [&](Edge A,Edge B) {
			return A.len < B.len;
		});

		int cost = 0;
		for (int i = 0; i < vec.size() && findfather(0) != findfather(m*n-1); i++) //把边一条条加入并查集
		{
			Edge e = vec[i];
			if (findfather(e.x) != findfather(e.y)) //如果之前两个点不连通
			{
				Union(e.x, e.y);
				cost = e.len; //目前最大花销
			}
		}
		return cost;
	}
private:
	int findfather(int x)
	{
		return x == father[x] ? x : father[x] = findfather(father[x]);
	}

	void Union(int a,int b)
	{
		int A = findfather(a);
		int B = findfather(b);

		if (A != B)
			father[B] = A;
	}
};

765. 情侣牵手 H
N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。
人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。
这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。

示例 1:
输入: row = [0, 2, 1, 3]
输出: 1
解释: 我们只需要交换row[1]和row[2]的位置即可。
示例 2:

输入: row = [3, 2, 0, 1]
输出: 0
解释: 无需交换座位,所有的情侣都已经可以手牵手了。

思路:
一开始想到并查集,但是没法把它抽象。主要是没法套用给的示例。
所以其实这题要画三对情侣(6个人)比较好理解,相邻的位置必须是0,1 2,3这种,而不能是1,2这种,因为如果凑出了1,2,0,3必定被孤立了。所以这题题解显示,可以把座位(二连坐)看成是节点。

1.把n/2个座位初始化father数组。
2.如果相邻的row[i]和row[i+1]是相邻的(这里巧妙的用/2向下取整来表示了),则什么都不做(因为取整完是同一个节点)。
3.如果row[i]和row[i+1]不相邻,则Union第i和第j个座位。
4.对于每个连通分量来说,需要的最小交换次数就是连通分量内节点的数量 - 1。

在这里插入图片描述

class Solution {
public:
	int minSwapsCouples(vector<int>& row) {
		int n = row.size();
		int site = n / 2; //每个座位看成一个节点
		vector<int> father(site);
		for (int i = 0; i < site; i++) //初始化
			father[i] = i;

		for (int i = 0; i < n; i += 2) //两个人一跳
		{
			int l = row[i] / 2; //如果相邻两个人不是情侣,则相连i,j座位节点
			int r = row[i + 1] / 2; //如果相邻两个人是情侣,则什么也不做
			Union(father, l, r);
		}

		unordered_map<int, int> mp;
		for (int i = 0; i < site; i++) //计算连通分量
		{
			int fx = findfather(father,i);
			mp[fx]++;
		}

		int res = 0;
		//每个连通分量的连通座位数-1就是该分量内的交换次数
		for (auto it = mp.begin(); it != mp.end(); it++)
			res += it->second - 1;

		return res;

	}

private:
	int findfather(vector<int>& father,int x)
	{
		return x == father[x] ? x : father[x] = findfather(father,father[x]);
	}

	void Union(vector<int>& father,int a,int b)
	{
		int A = findfather(father, a);
		int B = findfather(father, b);
		if (A != B)
			father[B] = A;
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值