给定n个不同元素的数组,找到排序数组所需的最小交换数。
输入:{ 4,3,2,1 }
输出:2 说明:将索引0与3交换,1与2交换以形成已排序的数组{1,2,3,4}。
输入:{1,5,4,3,2}
输出:2
通过将问题可视化为图形,可以轻松完成此操作。如果第i个索引处的元素必须出现在排序数组中的第j个索引处,我们将有n个节点和从节点i到节点j的边。
该图现在将包含许多非交叉循环。现在,具有2个节点的循环将仅需要1个交换以达到正确的排序,类似地,具有3个节点的循环将仅需要2个交换来执行此操作。
因此有:
k表示图中环的个数,cycle_size表示环内节点的大小.
因此就有:
public static int minSwaps(List<Integer> A){
int count = 0;
Map<Integer,Integer> map = new HashMap<Integer, Integer>();
List<Integer> B = new ArrayList<>(A);
Collections.sort(B);
for(int i = 0; i < B.size();i++){
map.put(B.get(i), i);
}
//DFS
ArrayList<Integer> C = new ArrayList<>(A);
ArrayList<Integer> D = new ArrayList<>();
for(int i = 0; i < C.size();i++){
if(D.contains(C.get(i)))continue;
int cycleSize = 1;
if(C.get(i)!=B.get(i)){
D.add(C.get(i));
int a = C.get(i);
while(true){
int curA = map.get(a);//当前元素的正确位置
int num = C.get(curA);//当前元素的正确位置上实际的元素
int curNum = map.get(num);//当前元素的正确位置上的实际元素的正确位置(是否能形成cycle)
cycleSize++;
D.add(num);
if(curNum==i){
break;
}
a=num;
}
count+=(cycleSize-1);
}
}
return count;
}
另一种类似的解法,将数组先排序,以<key,value>的形式保存到Map中,遍历未排序的数组,将元素逐个和已排序数组对应位置元素比较,若相等则跳过,不等则从Map中取出已排序元素的index更新未排序元素的value上(交换操作),同时swap list中的元素.这样遍历完成之后,就得到整个交换过程.代码如下:
public static List<List> minSwaps(List<Integer> A) {
List<Integer> B = new ArrayList<>(A);
Collections.sort(B);
Map<Integer, Integer> valToPos = new HashMap<>();
for(int i = 0; i < A.size(); ++i)
valToPos.put(A.get(i), i);
List<List> swaps = new ArrayList<>();
swaps.add(new ArrayList<>(A));
for(int i = 0; i < A.size(); ++i) {
if(B.get(i) != A.get(i)) {
int posBinA = valToPos.get(B.get(i));
int posCurrA = i;
valToPos.put(B.get(i), posCurrA);
valToPos.put(A.get(i), posBinA);
Collections.swap(A, posCurrA, posBinA);
swaps.add(new ArrayList<>(A));
}
}
return swaps;
}