背景
给一个数组,计算得到完全递增结果的需要的最小交换次数
什么是置换环
置换环的思想为 : 对每个节点,将其指向其排序后应该放到的位置,直到首位相接形成了一个环。
这道问题有几个思考点
- 选择排序时间复杂度是o(n^2)
- 求交换次数不需要真的交换数组中的值
- 置换环为什么要讲已经排序好的值放到一个map中,而不是未排序好的值放到一个map中
置换环算法模版
// 返回使得 nums 递增需要的最小交换元素次数
public int minChanges(int[] nums){
int[] copy = Arrays.copyOf(nums, nums.length);
Arrays.sort(copy);
HashMap<Integer, Integer> map = new HashMap<>();
for(int i=0; i<copy.length; i++){
map.put(copy[i], i);
}
boolean[] flag = new boolean[nums.length]; // 用于标记 nums[i] 是否已经被加入环中
int loop = 0; // 环的个数
for(int i=0; i<nums.length; i++){
if(!flag[i]){
int j = i;
while(!flag[j]){ // 画环
int index = map.get(nums[j]); // 当前节点指向的位置,画环过程
flag[j] = true; // 将 j 加入环中
j = index; // 将当前节点移动到环上下个节点
}
loop++; // 环数递增
}
}
return nums.length - loop; // 最小交换次数为 : 数组长度 - 环数
}
思考为什么保存的是排序后的值
public int minimumOperations(int[] nums) {
int cnt = 0, n = nums.length;
int[] sorted = Arrays.copyOf(nums, n);
Arrays.sort(sorted);
// Map<num, idx> 记录值正确的索引下标
Map<Integer, Integer> rightIdxMap = new HashMap<>();
for (int i = 0; i < n; i++) {
rightIdxMap.put(sorted[i], i);
}
for (int i = 0; i < n; i++) {
//目的在于讲nums[i]放到正确的索引,所以这里要用while循环
while (nums[i] != sorted[i]) {
// 不在正确的位置则交换到正确的位置
int rightIdx = rightIdxMap.get(nums[i]);
swap(nums, i, rightIdx);
cnt++;
}
}
return cnt;
}
public void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
如果保存是未排序的值的下标,会出现将其交换之后map保存的下标不对,所以代码里才使用while循环,将其当前下表i对应的值放到正确位置上
`错误的想法``
public int minimumOperations(int[] nums) {
int cnt = 0, n = nums.length;
int[] sorted = Arrays.copyOf(nums, n);
Arrays.sort(sorted);
// Map<num, idx> 记录值正确的索引下标
Map<Integer, Integer> rightIdxMap = new HashMap<>();
for (int i = 0; i < n; i++) {
rightIdxMap.put(nums[i], i);
}
for (int i = 0; i < n; i++) {
if (nums[i] != sorted[i]) {
// 不在正确的位置则交换到正确的位置
int rightIdx = rightIdxMap.get(sorted[i]);
//swap之后map需要重新构建
swap(nums, i, rightIdx);
cnt++;
}
}
return cnt;
}
public void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}