题意整理
- 给定一个长度为n的排列p,以及m个数对,每个数对包含一个
和一个
。
- 可以将排列p中
下标对应值和
下标对应值互换,数对的使用顺序和次数不限。
- 求任意次操作后字典序最小的排列p。
方法一(并查集)
1.解题思路
并查集简述:
并查集主要用于解决图中的连通域问题,起初并查集里包含n个节点,每个节点都有一个根,初始情况下,根指向自身。然后有两个关键方法,一个是find,一个是union。find用于查找当前节点的根,union用于连接两个节点,连接以后处于同一个连通域,即共享一个根。
- 首先新建并查集,遍历Pair数组,连接对应节点。
- 再新建两个邻接表,一个用于记录当前根对应连通域所有节点的下标,另一个用于记录当前根对应连通域所有节点的值。
- 遍历所有可能的根,然后对连通域内的节点值排序,赋值到perm数组。直到遍历完所有的根。
动图展示:
2.代码实现
import java.util.*;
/*
* public class Point {
* int x;
* int y;
* }
*/
public class Solution {
/**
* 返回牛牛所能得到的字典序最小的排列
* @param n int整型
* @param m int整型
* @param perm int整型一维数组
* @param Pair Point类一维数组
* @return int整型一维数组
*/
public int[] solve (int n, int m, int[] perm, Point[] Pair) {
//新建并查集
UF uf=new UF(n);
//连接对应节点
for(Point point:Pair){
uf.union(point.x-1,point.y-1);
}
//初始化邻接表,分别记录可交换节点的Id和Value
List<List<Integer>> IdGraph=new ArrayList<>();
List<List<Integer>> ValueGraph=new ArrayList<>();
for(int i=0;i<n;i++){
IdGraph.add(new ArrayList<>());
ValueGraph.add(new ArrayList<>());
}
for(int i=0;i<n;i++){
IdGraph.get(uf.find(i)).add(i);
ValueGraph.get(uf.find(i)).add(perm[i]);
}
for(int i=0;i<n;i++){
//找到可交换的所有节点,将其Value进行排序
Collections.sort(ValueGraph.get(i));
for(int j=0;j<IdGraph.get(i).size();j++){
//将排序后的节点写入perm排列,保证还是那些Value对应的Id
perm[IdGraph.get(i).get(j)]=ValueGraph.get(i).get(j);
}
}
return perm;
}
}
//并查集结构
class UF{
//父数组
int[] parent;
UF(int n){
//初始化
parent=new int[n];
for(int i=0;i<n;i++){
parent[i]=i;
}
}
//连接p节点和q节点
public void union(int p,int q){
//首先找打对应根节点,然后将其中一个节点的父置为另一个节点
int pRoot=find(p);
int qRoot=find(q);
if(pRoot==qRoot) return;
parent[pRoot]=qRoot;
}
//寻找根节点
public int find(int x){
//如果根节点不是自身,则将其父节点置为父节点的父节点
while(x!=parent[x]){
parent[x]=parent[parent[x]];
//继续沿父节点往上找
x=parent[x];
}
return x;
}
} 3.复杂度分析
- 时间复杂度:最坏情况下,并查集union操作的时间复杂度为
,需要进行m次,于是所有union操作的时间复杂度是
,然后假设并查集最后有k个根,每个根连通域大小为
,则时间复杂度为
,所以最终的时间复杂度为
。
- 空间复杂度:所有连通域的总节点个数最多为n,所以空间复杂度为
。
方法二(按秩合并)
1.解题思路
基本思路和方法一相同。对并查集的union操作进行了优化。原来的union方式会导致某些节点离根越来越远,从而find方法耗时越来越长。假设现在有两个连通域,一个连通域根是pRoot,大小是m,另一个连通域根是qRoot,大小是n,并且m<n。如果连接pRoot和qRoot,将pRoot作为新连通域的根,则会有n个节点的查找路径长度加一,如果将qRoot作为新连通域的根,则会有m个节点的查找路径长度加一,显然让qRoot作为新的根,对find效率的影响会更小一点。
2.代码实现
import java.util.*;
/*
* public class Point {
* int x;
* int y;
* }
*/
public class Solution {
/**
* 返回牛牛所能得到的字典序最小的排列
* @param n int整型
* @param m int整型
* @param perm int整型一维数组
* @param Pair Point类一维数组
* @return int整型一维数组
*/
public int[] solve (int n, int m, int[] perm, Point[] Pair) {
//新建并查集
UF uf=new UF(n);
//连接对应节点
for(Point point:Pair){
uf.union(point.x-1,point.y-1);
}
//初始化邻接表,分别记录可交换节点的Id和Value
List<List<Integer>> IdGraph=new ArrayList<>();
List<List<Integer>> ValueGraph=new ArrayList<>();
for(int i=0;i<n;i++){
IdGraph.add(new ArrayList<>());
ValueGraph.add(new ArrayList<>());
}
for(int i=0;i<n;i++){
IdGraph.get(uf.find(i)).add(i);
ValueGraph.get(uf.find(i)).add(perm[i]);
}
for(int i=0;i<n;i++){
//找到可交换的所有节点,将其Value进行排序
Collections.sort(ValueGraph.get(i));
for(int j=0;j<IdGraph.get(i).size();j++){
//将排序后的节点写入perm排列,保证还是那些Value对应的Id
perm[IdGraph.get(i).get(j)]=ValueGraph.get(i).get(j);
}
}
return perm;
}
}
//并查集结构
class UF{
//父数组
int[] parent;
//秩数组
int[] size;
UF(int n){
//初始化
parent=new int[n];
size=new int[n];
for(int i=0;i<n;i++){
parent[i]=i;
size[i]=1;
}
}
//连接p节点和q节点
public void union(int p,int q){
//首先找打对应根节点,然后将其中一个节点的父置为另一个节点
int pRoot=find(p);
int qRoot=find(q);
if(pRoot==qRoot) return;
//将秩较小的根指向秩较大的根
if(size[pRoot]<size[qRoot]){
parent[pRoot]=qRoot;
}
else{
parent[qRoot]=pRoot;
}
}
//寻找根节点
public int find(int x){
//如果根节点不是自身,则将其父节点置为父节点的父节点
while(x!=parent[x]){
parent[x]=parent[parent[x]];
//继续沿父节点往上找
x=parent[x];
}
return x;
}
} 3.复杂度分析
- 时间复杂度:最坏情况下,并查集union操作的时间复杂度为
,需要进行m次,于是所有union操作的时间复杂度是
,然后假设并查集最后有k个根,每个根连通域大小为
,则时间复杂度为
,所以最终的时间复杂度为
。
- 空间复杂度:所有连通域的总节点个数最多为n,所以空间复杂度为
。

京公网安备 11010502036488号