并查集并查集

并查集就是一个一群人找祖宗的过程:

最开始,每个人就是一个单独的部落,

根据某个条件判断他俩属于一个部落,比如他俩相似,或者有其它属性,

属于一个部落的两个人,就有一个祖宗,

找到同宗的,那就部落合并,最开始的部落数就可以-1了

遍历到最后的最后,所有人都认祖归宗,那么部落数就是最后的结果

    /**
     * 剑指offer 相似的字符串
     *
     * 【定义】:
     * 如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,那么称 X 和 Y 两个字符串相似。
     * 如果这两个字符串本身是相等的,那它们也是相似的。
     *
     * 给定一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个 字母异位词 。请问 strs 中有多少个相似字符串组
     *
     * 比如由 "a","r","s","t"组成的单词,会形成两个关联组:
     * {"tars", "rats", "arts"} 和 {"star"}
     * 因为,"tars"和"arts"都分别和"rats"关联,因此他们可以在一个组
     *
     * 其实就是一个unionfind的过程 或者 广度优先遍历并记录有多少个联通分量
     */
    public int numSimilarGroups(String[] strs) {
        // 有多少个单词
        int n = strs.length;
        int cnt = n;
        // 这就是在看到最后有多少完全独立的祖宗,就是有多少组了
        int[] fathers = new int[n];
        // 初始化
        for (int i = 0; i < n; i++) {
            // 最开始的时候所有单词的祖宗都是独立的
            fathers[i] = i;
        }

        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                // 然后开始两两遍历string组合,如果这俩是similar的就union起来
                if (isSimilar(strs[i], strs[j]) && union(fathers, i, j)) {
                    // 部落减少一个,因为合并了
                    cnt--;
                }
            }
        }
        return cnt;
    }

    /**
     * 准备并查集的方法
     */
    private boolean isSimilar(String s1, String s2) {
        int cnt = 0;
        for (int i = 0; i < s1.length(); i++) {
            if (s1.charAt(i) != s2.charAt(i)) {
                cnt++;
            }
        }
        return cnt <= 2;
    }

    /**
     * 并查集的准备工作
     */
    private int findFather(int[] fathers, int i) {
        if (fathers[i] != i) {
            // 就是一直向上查找,找到祖宗
            fathers[i] = findFather(fathers, fathers[i]);
        }
        return fathers[i];
    }

    private boolean union(int[] fathers, int i, int j) {
        int a = findFather(fathers, i);
        int b = findFather(fathers, j);
        if (a != b) {
            // 因为要把i和j联系起来
            fathers[a] = b;
            return true;
        }
        return false;
    }

上面这个题也可以使用图的联通分量做

将strs这个数组中string的相似关系画成一张图,再根据广度优先遍历方法去遍历这个图。在遍历的过程中,每次遇到没有遍历过的node,就意味着开辟了一个新部落,此时cnt++。

最终遍历完所有的string时,cnt是多少就证明有多少个新部落

    public int numSimilarGroupsGraph(String[] strs) {
        int n = strs.length;
        int cnt = 0;
        boolean[] isVisited = new boolean[n];
        Map> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            map.put(strs[i], new ArrayList<>());
        }

        // 给每一个strs中的string找一个他可以联通的所有单词的list
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (isSimilar(strs[i], strs[j])) {
                    map.get(strs[i]).add(strs[j]);
                }
            }
        }

        // 然后对构建的这个无向图使用bfs寻找联通分量
        for (int i = 0; i < n; i++) {
            if (!isVisited[i]) {
                bfs(strs, isVisited, i, map);
                cnt++;
            }
        }
        return cnt;
    }

    private void bfs(String[] strs, boolean[] isVisited, int i, Map> map) {
        Queue queue = new LinkedList<>();
        queue.offer(i);
        isVisited[i] = true;
        while (!queue.isEmpty()) {
            int node = queue.poll();
            for (int j = 0; j < strs.length; j++) {
                if (!isVisited[j] && map.get(strs[node]).contains(strs[j])) {
                    // 这俩string是联通的,则j这个位置的string可以作为下一个遍历的node
                    queue.offer(j);
                    isVisited[j] = true;
                }
            }
        }
    }

你可能感兴趣的:(java,后端,数据结构)