每日一道leetcode(2026.04.21):执行交换操作后的最小汉明距离

0 阅读6分钟

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;
    }
}