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;
}
};