思路:
题目的主要信息:
- 给出一个长为n的排列p,即1到n的任意一个组合
- 一共有m对
,每对表示交换排列p中序号为
和
中的元素,注意是序号而不是下标
- m对
使用次数与顺序不受限制,求任意次操作之后能得到的字典序最小的排列是什么
方法一:并查集
具体做法:
可以用并查集的思想来解决,解释一下为何是并查集:假设排列p中两个位置a与b可以交换,如果b与c还可以交换,那么事实上a与c也可以交换,即我们只要找到每个独立的可以交换的集合,单独对集合内的元素排序,然后从小到大只排列集合中这些位置,就可以得到集合中我们要求的字典序最小。处理完每一个集合就是我们的全部字典序最小。
而要将所有单独的元素元素合并成各自的集合,就是并查集的内容。我们写一个并查集类,初始化为一个0到n-1的排列,对应每个节点的下标,并查集中有一个根节点,find函数找到每个节点的根节点,_union函数合并根节点一样的元素。然后我们用两个邻接表记录每个集合的下标和数值,遍历每个集合的邻接表,对其两部分排序,依次在其相应位置加入最小的元素就可以得到字典序最小。
#include <numeric> //iota函数的头文件
class DSU{ //建立并查集的类
public:
DSU(int n) : p(n){ //初始化n个集合
iota(begin(p), end(p), 0); //从0开始填充到n-1
}
int find(int x){ //找最大父节点
if (p[x] == x)
return x;
return p[x] = find(p[x]); //递归查找是否存在
}
void _union(int u, int v) {
int pu = find(u); //相互找到最大关联是否统一
int pv = find(v);
if (pu == pv) //已经合并了
return;
p[pu] = pv;
}
vector<int> p;
};
class Solution {
public:
vector<int> solve(int n, int m, vector<int>& perm, vector<Point>& Pair) {
DSU dsu(n);
vector<int> res(n);
for(int i = 0; i < m; i++) //将所有的可以交换的元素合并
dsu._union(Pair[i].x - 1, Pair[i].y - 1); //下标需要序号减1
vector<vector<int> > Gid(n);
vector<vector<int> > Gval(n);
for(int i = 0; i < n; i++){
Gid[dsu.find(i)].push_back(i); //某一块连通集合对应的下标有哪些
Gval[dsu.find(i)].push_back(perm[i]); //某一块连通集合对应的数值有哪些
}
for(int i = 0; i < n; i++){
sort(Gid[i].begin(), Gid[i].end());
sort(Gval[i].begin(), Gval[i].end()); //对同一集合数值排序
for(int j = 0; j < Gid[i].size(); j++) //依次加入小的到答案中,字典序就会更小
res[Gid[i][j]] = Gval[i][j];
}
return res;
}
};复杂度分析:
- 时间复杂度:
,最坏情况下并查集合并函数复杂度为
,一共
次,后续添加部分主体是排序,我们假设一共有
个连通集合,其中每个集合的元素个数为
,则排序总时间为
,其中
,其余复杂度低于这个值故舍去
- 空间复杂度:
,并查集类的大小及其他辅助数组长度均为
方法二:dfs染色
具体做法:
对于并查集,我们也可以用染色的方式来完成,我们将可以交换的位置视作图的节点,构建图的连通表。然后遍历每个节点,对没有染色的节点进行dfs遍历整个连通的部分,染成相同的颜色,记录这个数字,并记录这部分的下标和数值,然后还是排序取最小使字典序最小。用数字表示颜色,数字为0表示未染色未访问过,每次dfs前数字加1代表换颜色。
class Solution {
public:
int count = 0;
void dfs(int v, int color, vector<int>& perm, vector<int>& label, vector<int>& id, vector<int>& val, vector<vector<int>>& G) {
label[v] = color; //标记本块颜色
id[count] = v; //记录id
val[count] = perm[v]; //记录数值
count++;
for(int i = 0; i < G[v].size(); i++){ //相邻点染同色
if (label[G[v][i]] == 0) //必须是没访问过的
dfs(G[v][i], color, perm, label, id, val, G);
}
}
vector<int> solve(int n, int m, vector<int>& perm, vector<Point>& Pair) {
vector<vector<int> > G(n);
vector<int> val(n, 0);
vector<int> id(n, 0);
vector<int> label(n, 0); //标记每个节点的颜色,颜色相同则连通
for (int i = 0; i < m; i++) { //构建图
G[Pair[i].x - 1].push_back(Pair[i].y - 1); //下标减1
G[Pair[i].y - 1].push_back(Pair[i].x - 1);
}
int color = 0; //记录颜色的种类
for (int i = 0; i < n; i++) { //遍历每个节点
if (label[i] == 0){ //新的颜色,新的连通块
color++;
count = 0; //记录本次集合的元素个数
dfs(i, color, perm, label, id, val, G); //dfs
sort(val.begin(), val.begin() + count);
sort(id.begin(), id.begin() + count);
for (int j = 0; j < count; j++) //排序后相同集合小的在前
perm[id[j]] = val[j];
}
}
return perm;
}
};复杂度分析:
- 时间复杂度:
,构建图一共循环
次,后续添加部分主体是排序,我们假设一共有
个连通集合,其中每个集合的元素个数为
,则排序总时间为
,其中
,其余复杂度如dfs总共遍历
次,低于这个值故舍去
- 空间复杂度:
,辅助数组长度及递归栈深度最大为

京公网安备 11010502036488号