1722. 执行交换操作后的最小汉明距离(并查集求解连通分量)

题目

给你两个整数数组 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 <= 105
1 <= source[i], target[i] <= 105
0 <= allowedSwaps.length <= 105
allowedSwaps[i].length == 2
0 <= ai, bi <= n - 1
ai != bi

解析

首先,我们来分析所谓的交换,有怎样的性质。因为我们执行交换的顺序和次数都是任意的,因此,如果0,1可以互相交换,而1,2可以互相交换,那就代表着0,1,2都可以互相交换。那么,如果我们单独看0,1,2这几个位置,那么这三个位置的汉明距离其实取决于source和target之间有多少个相异的字母。那么,我们要解决这个问题,其实可以看下整个数据的下标可以分成几个类似于0,1,2这样完全可以交换的组,而每个组中又有多少个相异的字母。接着,我们就想到,其实这个问题可以看作0,1,...,n都是图的节点,0,1可交换,就代表图的节点间是相连的,而我们的问题就是求这个图中有哪几个连通分量,到这里,解决问题的手段就很明显了,我们使用并查集,来寻找所有的联通分量。

于是就有了我第一个版本的代码。

class Solution {
    class UnionFind {
        private int[] id;
        private int count;
        private int[] sz;

        public UnionFind(int N) {
            count = N;
            id = new int[N];
            sz = new int[N];
            for(int i = 0; i < N; i++) {
                id[i] = i;
                sz[i] = 1;
            }
        }

        public int getCount() {
            return count;
        }

        public boolean connected(int p, int q) {
            return find(p) == find(q);
        }

        public int find(int p) {
            if (p != id[p]) id[p] = find(id[p]);
            return id[p];
        }

        public void union(int p, int q){
            int pRoot = find(p);
            int qRoot = find(q);

            if(pRoot == qRoot) return;

            if(sz[pRoot] < sz[qRoot]) { id[pRoot] = qRoot; sz[qRoot] += sz[pRoot]; }
            else                      { id[qRoot] = pRoot; sz[pRoot] += sz[qRoot]; }
            count--;
        }
    }

    public int minimumHammingDistance(int[] source, int[] target, int[][] allowedSwaps) {
        int n = source.length;
        UnionFind uf = new UnionFind(n);
        for(int i=0;i<allowedSwaps.length;i++){
            uf.union(allowedSwaps[i][0],allowedSwaps[i][1]);
        }
        int res = 0;
        boolean[] visited = new boolean[n];
        for(int i=0;i<n;i++){
            if(visited[i]){
                continue;
            }
            Set<Integer> s = new HashSet<Integer>();
            Set<Integer> t = new HashSet<Integer>();
            s.add(source[i]);
            t.add(target[i]);
            for(int j=i+1;j<n;j++){
                if(uf.connected(i,j)){
                    s.add(source[j]);
                    t.add(target[j]);
                    visited[j] = true;
                }
            }
            Set<Integer> ex = new HashSet<Integer>();
            ex.addAll(s);
            ex.removeAll(t);
            res = res + ex.size();
        }
        return res;
    }
}

很可惜,这个代码只要报了解答错误。错在哪里呢?错在source和target中都有重复的元素,而我使用set求差集是默认把重复元素全部忽略掉的。因此,需要用更复杂的数据结构,我想到,我可以用一个HashMap存储source元素和它出现的次数。对于target呢我先用一个数组存储所有元素,然后一一比对,遇到相异的就把结果+1,而对应元素出现次数-1,这样就避免了set忽略重复元素的情况。这就是我第二个版本的代码。

class Solution {
    class UnionFind {
        public int[] id;
        private int count;
        private int[] sz;

        public UnionFind(int N) {
            count = N;
            id = new int[N];
            sz = new int[N];
            for(int i = 0; i < N; i++) {
                id[i] = i;
                sz[i] = 1;
            }
        }

        public int getCount() {
            return count;
        }

        public boolean connected(int p, int q) {
            return find(p) == find(q);
        }

        public int find(int p) {
            if (p != id[p]) id[p] = find(id[p]);
            return id[p];
        }

        public void union(int p, int q){
            int pRoot = find(p);
            int qRoot = find(q);

            if(pRoot == qRoot) return;

            if(sz[pRoot] < sz[qRoot]) { id[pRoot] = qRoot; sz[qRoot] += sz[pRoot]; }
            else                      { id[qRoot] = pRoot; sz[pRoot] += sz[qRoot]; }
            count--;
        }
    }

    public int minimumHammingDistance(int[] source, int[] target, int[][] allowedSwaps) {
        int n = source.length;
        UnionFind uf = new UnionFind(n);
        for(int i=0;i<allowedSwaps.length;i++){
            uf.union(allowedSwaps[i][0],allowedSwaps[i][1]);
        }
        Set<Integer> connectedComponent = new HashSet<Integer>();
        int res = 0;
        for(int i=0;i<n;i++){
            int root = uf.find(i);
            connectedComponent.add(root);
        }
        for(int i:connectedComponent){
            Map<Integer, Integer> s = new HashMap<Integer, Integer>();
            ArrayList<Integer> t = new ArrayList<Integer>();
            s.put(source[i], 1);
            t.add(target[i]);
            for(int j=0;j<n;j++){
                if(i==j) continue;
                if(uf.connected(i,j)){
                    if(s.containsKey(source[j])){
                        int value = s.get(source[j]);
                        s.put(source[j], value+1);
                    } else {
                        s.put(source[j], 1);
                    }
                    t.add(target[j]);
                }
            }
            for(int j=0;j<t.size();j++){
                int value = t.get(j);
                if(s.containsKey(value)){
                    int appearTime = s.get(value);
                    appearTime--;
                    if(appearTime==0){
                        s.remove(value);
                    } else {
                        s.put(value, appearTime);
                    }
                } else {
                    res++;
                }
            }
            //for(int value:s.values()){
            //    res = res + value;
            //}
        }
        return res;
    }
}

很可惜,这段代码超时了,仔细看了一下,这里面确实有进一步优化的空间,没有必要有两个for循环。为了把其中一个for循环优化掉,我们需要Map<Integer, HashMap<Integer,Integer>>这么一个复杂的结构,key是当前节点对应的根节点,而后面的HashMap则继续存储我们前文中提到的数据。这样子,我们只需要一个for循环,就把所有的元素存储到我们希望的数据结构中去。最后再遍历一遍所有的key,把结果输出。搞定!

class Solution {
    class UnionFind {
        public int[] id;
        private int count;
        private int[] sz;

        public UnionFind(int N) {
            count = N;
            id = new int[N];
            sz = new int[N];
            for(int i = 0; i < N; i++) {
                id[i] = i;
                sz[i] = 1;
            }
        }

        public int getCount() {
            return count;
        }

        public boolean connected(int p, int q) {
            return find(p) == find(q);
        }

        public int find(int p) {
            if (p != id[p]) id[p] = find(id[p]);
            return id[p];
        }

        public void union(int p, int q){
            int pRoot = find(p);
            int qRoot = find(q);

            if(pRoot == qRoot) return;

            if(sz[pRoot] < sz[qRoot]) { id[pRoot] = qRoot; sz[qRoot] += sz[pRoot]; }
            else                      { id[qRoot] = pRoot; sz[pRoot] += sz[qRoot]; }
            count--;
        }
    }

    public int minimumHammingDistance(int[] source, int[] target, int[][] allowedSwaps) {
        int n = source.length;
        UnionFind uf = new UnionFind(n);
        for(int i=0;i<allowedSwaps.length;i++){
            uf.union(allowedSwaps[i][0],allowedSwaps[i][1]);
        }
        Map<Integer, HashMap<Integer,Integer>> s = new  HashMap<Integer, HashMap<Integer,Integer>>();
        Map<Integer, ArrayList<Integer>> t = new  HashMap<Integer, ArrayList<Integer>>();
        int res = 0;
        for(int i=0;i<n;i++){
            int root = uf.find(i);
            if(s.containsKey(root)){
                HashMap<Integer,Integer> sMap = s.get(root);
                if(sMap.containsKey(source[i])){
                    int value = sMap.get(source[i]);
                    sMap.put(source[i], value+1);
                } else {
                    sMap.put(source[i], 1);
                }
                ArrayList<Integer> tList = t.get(root);
                tList.add(target[i]);
                t.put(root,tList);
            } else {
                HashMap<Integer,Integer> tmpMap = new HashMap<Integer,Integer>();
                tmpMap.put(source[i],1);
                s.put(root,tmpMap);
                ArrayList<Integer> tmpList = new ArrayList<Integer>();
                tmpList.add(target[i]);
                t.put(root,tmpList);
            }
        }
        for(int key:s.keySet()){
            HashMap<Integer,Integer> sMap = s.get(key);
            ArrayList<Integer> tList = t.get(key);
            for(int i=0;i<tList.size();i++){
                int value = tList.get(i);
                if(sMap.containsKey(value)){
                    int appearTime = sMap.get(value);
                    appearTime--;
                    if(appearTime==0){
                        sMap.remove(value);
                    } else {
                        sMap.put(value, appearTime);
                    }
                } else {
                    res++;
                }
            }
        }
        return res;
    }
}

反思一下,好久不刷leetcode,感觉写代码的准确性,思维的活跃性都在急剧下降,以后要常常刷题活跃下脑筋了...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值