1. 题目
给你两个整数数组 source 和 target ,长度都是 n 。还有一个数组 allowedSwaps ,其中每个allowedSwaps[i] = [ai, bi] 表示你可以交换数组 source 中下标为 ai 和 bi(下标从 0 开始)的两个元素。注意,你可以按 任意 顺序 多次 交换一对特定下标指向的元素。
相同长度的两个数组 source 和 target 间的 汉明距离 是元素不同的下标数量。形式上,其值等于满足 source[i] != target[i] (下标从 0 开始)的下标 i(0 <= i <= n-1)的数量。
在对数组 source 执行 任意 数量的交换操作后,返回 source 和 target 间的 最小汉明距离 。
示例 1:
输入:source = [1,2,3,4], target = [2,1,4,5], allowedSwaps = [[0,1],[2,3]] 输出:1 解释:source 可以按下述方式转换:
- 交换下标 0 和 1 指向的元素:source = [2,1,3,4]
- 交换下标 2 和 3 指向的元素:source = [2,1,4,3] source 和 target 间的汉明距离是 1 ,二者有 1 处元素不同,在下标 3 。
示例 2:
输入:source = [1,2,3,4], target = [1,3,2,4], allowedSwaps = [] 输出:2 解释:不能对 source 执行交换操作。 source 和 target 间的汉明距离是 2 ,二者有 2 处元素不同,在下标 1 和下标 2 。
示例 3:
输入:source = [5,1,2,4,3], target = [1,5,4,2,3], allowedSwaps = [[0,4],[4,2],[1,3],[1,4]] 输出:0
提示:
n == source.length == target.length 1 <= n <= 10e5 1 <= source[i], target[i] <= 10e5 0 <= allowedSwaps.length <= 10e5 allowedSwaps[i].length == 2 0 <= ai, bi <= n - 1 ai != bi
2. 分析
这道题可能结合示例好理解题目一些,根据allowedSwaps二维数组里面给定的下标对来交换source数组中的元素,注意可以任意顺序多次交换,换言之,source下标集合如果在某个集合内,那么他们就是可以连通的,交换为任意的顺序,所以,问题则转换为将allowedSwaps中的数组转换为n个相互之间没有交集的集合。
本着先暴力破解,再优化的原则,我使用了多个Set集合来将不能移动,和可移动的多个集合进行处理,最后执行结果是超出时间限制。
class Solution {
public int minimumHammingDistance(int[] source, int[] target, int[][] allowedSwaps) {
// 未移动的索引集合
Set<Integer> unswapSet = new HashSet<>();
for (int i = 0; i < source.length; i++) {
unswapSet.add(i);
}
// 相互之间不连通的集合区域
List<Set<Integer>> scopeList = new ArrayList<>();
Arrays.sort(allowedSwaps, (o1, o2) -> Math.min(o1[0], o1[1]) - Math.min(o2[0], o2[1]));
for (int[] allowedSwap : allowedSwaps) {
int from = allowedSwap[0];
int to = allowedSwap[1];
// 从大集合中移除
unswapSet.remove(from);
unswapSet.remove(to);
Set<Integer> unionSet = null;
boolean isUnion = false;
Iterator<Set<Integer>> iterator = scopeList.iterator();
while (iterator.hasNext()) {
Set<Integer> set = iterator.next();
if (set.contains(from) || set.contains(to)) {
if (unionSet == null) {
// 加入第一个匹配上的集合
set.add(from);
set.add(to);
unionSet = set;
isUnion = true;
} else {
// 将当前的集合进行合并
unionSet.addAll(set);
iterator.remove();
}
}
}
if (!isUnion) {
// 新增集合
Set<Integer> set = new HashSet<>();
set.add(from);
set.add(to);
scopeList.add(set);
}
}
int minHammingDistance = 0;
// 对各个区域内的元素分布进行有序的比较
for (int val : unswapSet) {
if (source[val] != target[val]) {
minHammingDistance++;
}
}
for (Set<Integer> set : scopeList) {
minHammingDistance += hammingDistance(source, target, set);
}
return minHammingDistance;
}
public static int hammingDistance(int[] source, int[] target, Set<Integer> set) {
int minHammingDistance = 0;
// 对各个区域内的元素分布进行有序的比较
if (set.size() > 0) {
Map<Integer, Integer> map = new HashMap<>();
for (int v : set) {
int val = target[v];
int count = map.getOrDefault(val, 0);
if (count == 0) {
map.put(val, 1);
} else {
map.put(val, count + 1);
}
}
for (int v : set) {
int val = source[v];
int count = map.getOrDefault(val, 0);
if (count == 0) {
minHammingDistance++;
} else {
map.put(val, count - 1);
}
}
}
return minHammingDistance;
}
}
然后想着使用int数组来替换Set等高级容器操作,效率应该会有提升,然后又遇到了超出内存限制。
public int minimumHammingDistance(int[] source, int[] target, int[][] allowedSwaps) {
// 未移动的索引集合
int[] notSwapArray = new int[source.length];
Arrays.fill(notSwapArray, 1);
// 相互之间不连通的集合区域
List<int[]> scopeArrayList = new ArrayList<>();
Arrays.sort(allowedSwaps, (o1, o2) -> Math.min(o1[0], o1[1]) - Math.min(o2[0], o2[1]));
for (int[] allowedSwap : allowedSwaps) {
int from = allowedSwap[0];
int to = allowedSwap[1];
// 从大集合中移除
notSwapArray[from] = 0;
notSwapArray[to] = 0;
int[] unionArray = null;
boolean isUnion = false;
Iterator<int[]> iterator1 = scopeArrayList.iterator();
while (iterator1.hasNext()) {
int[] scopeArray = iterator1.next();
if (scopeArray[from] > 0 || scopeArray[to] > 0) {
if (unionArray == null) {
// 加入第一个匹配上的集合
scopeArray[from]++;
scopeArray[to]++;
unionArray = scopeArray;
isUnion = true;
} else {
// 将当前的集合进行合并
for (int i = 0; i < scopeArray.length; i++) {
unionArray[i] += scopeArray[i];
}
iterator1.remove();
}
}
}
if (!isUnion) {
int[] scopeArray = new int[source.length];
scopeArray[from]++;
scopeArray[to]++;
scopeArrayList.add(scopeArray);
}
}
int minHammingDistance = 0;
// 对各个区域内的元素分布进行有序的比较
for (int i = 0; i < notSwapArray.length; i++) {
if (notSwapArray[i] == 1 && source[i] != target[i]) {
minHammingDistance++;
}
}
for (int[] array : scopeArrayList) {
minHammingDistance += hammingDistance(source, target, array);
}
return minHammingDistance;
}
public static int hammingDistance(int[] source, int[] target, int[] array) {
int minHammingDistance = 0;
// 对各个区域内的元素分布进行有序的比较
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < array.length; i++) {
if (array[i] == 0) {
continue;
}
int val = target[i];
int count = map.getOrDefault(val, 0);
if (count == 0) {
map.put(val, 1);
} else {
map.put(val, count + 1);
}
}
for (int i = 0; i < array.length; i++) {
if (array[i] == 0) {
continue;
}
int val = source[i];
int count = map.getOrDefault(val, 0);
if (count == 0) {
minHammingDistance++;
} else {
map.put(val, count - 1);
}
}
return minHammingDistance;
}
超出时间限制其实比较好理解,Set的各种方法,比array通过下标访问,天然慢了许多。超出内存限制则是在source数组的长度较大且集合较多时,新创建出来的数组都是这个长度,导致内存空间消耗较大。
基于这两点,又进一步做了优化,在使用数组来记录不同集合数据的基础上,尽量少的创建新数组。
3. 代码实现
class Solution {
public int minimumHammingDistance(int[] source, int[] target, int[][] allowedSwaps) {
// 未移动的索引集合
int[] notSwapArray = new int[source.length];
// 有序遍历,减少合并的情况
Arrays.sort(allowedSwaps, (o1, o2) -> Math.min(o1[0], o1[1]) - Math.min(o2[0], o2[1]));
int scopeIndex = 0;
for (int[] allowedSwap : allowedSwaps) {
int from = allowedSwap[0];
int to = allowedSwap[1];
if (notSwapArray[from] == 0 && notSwapArray[to] == 0) {
// 新的集合
scopeIndex++;
notSwapArray[from] = scopeIndex;
notSwapArray[to] = scopeIndex;
} else if (notSwapArray[from] == 0 && notSwapArray[to] != 0) {
// 加入已有的集合
notSwapArray[from] = notSwapArray[to];
} else if (notSwapArray[from] != 0 && notSwapArray[to] == 0) {
notSwapArray[to] = notSwapArray[from];
} else if (notSwapArray[from] != notSwapArray[to]) {
// 将原有的两个容器进行连通
int min = Math.min(notSwapArray[from], notSwapArray[to]);
int max = Math.max(notSwapArray[from], notSwapArray[to]);
// 将所有的max替换为min
for (int i = 0; i < notSwapArray.length; i++) {
if (notSwapArray[i] == max) {
notSwapArray[i] = min;
}
}
}
}
int minHammingDistance = 0;
// 对各个区域内的元素分布进行有序的比较
for (int i = 0; i < notSwapArray.length; i++) {
// 比较未移动的元素
if (notSwapArray[i] == 0 && source[i] != target[i]) {
minHammingDistance++;
}
}
Map<Integer, List<Integer>> map = new HashMap<>();
for (int i = 0; i < notSwapArray.length; i++) {
int val = notSwapArray[i];
if (val == 0) {
continue;
}
if (!map.containsKey(val)) {
map.put(val, new ArrayList<>());
}
map.get(val).add(i);
}
for (List<Integer> value : map.values()) {
minHammingDistance += hammingDistance(source, target, value);
}
return minHammingDistance;
}
public static int hammingDistance(int[] source, int[] target, List<Integer> list) {
int minHammingDistance = 0;
// 对各个区域内的元素分布进行有序的比较
Map<Integer, Integer> map = new HashMap<>();
for (Integer value : list) {
int val = target[value];
int count = map.getOrDefault(val, 0);
if (count == 0) {
map.put(val, 1);
} else {
map.put(val, count + 1);
}
}
for (Integer integer : list) {
int val = source[integer];
int count = map.getOrDefault(val, 0);
if (count == 0) {
minHammingDistance++;
} else {
map.put(val, count - 1);
}
}
return minHammingDistance;
}
}
4. 总结
基于这个思路去做,最终优化效果擦边通过。
在算法这个层面,能用原生数组去实现的逻辑,不管是一位还是多维数组,都尽量选数组,特别是见到提示里面的数据范围在10e5这个量级时。
只与那些特殊的技巧,就只能慢慢积累了。比如这道题,官方题解使用的是哈希表+并查集,执行效率比我的实现方案优很多。
class Solution {
private int[] fa;
private int[] rank;
private int find(int x) {
if (fa[x] != x) {
fa[x] = find(fa[x]);
}
return fa[x];
}
private void union(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return;
if (rank[x] < rank[y]) {
int temp = x;
x = y;
y = temp;
}
fa[y] = x;
if (rank[x] == rank[y]) {
rank[x]++;
}
}
public int minimumHammingDistance(int[] source, int[] target, int[][] allowedSwaps) {
int n = source.length;
fa = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) {
fa[i] = i;
}
for (int[] pair : allowedSwaps) {
union(pair[0], pair[1]);
}
Map<Integer, Map<Integer, Integer>> sets = new HashMap<>();
for (int i = 0; i < n; i++) {
int f = find(i);
sets.putIfAbsent(f, new HashMap<>());
Map<Integer, Integer> cnt = sets.get(f);
cnt.put(source[i], cnt.getOrDefault(source[i], 0) + 1);
}
int ans = 0;
for (int i = 0; i < n; i++) {
int f = find(i);
Map<Integer, Integer> cnt = sets.get(f);
if (cnt.getOrDefault(target[i], 0) > 0) {
cnt.put(target[i], cnt.get(target[i]) - 1);
} else {
ans++;
}
}
return ans;
}
}