力扣leetcode刷题:并查集相关题目

这里写目录标题

  • 并查集的思想与介绍
  • 力扣721.账户合并
    • 题目描述
    • 题目解读 方法:哈希表+并查集
    • 详细代码和注释
  • 力扣839.相似字符串组
    • 题目描述
    • 题目解读 方法:并查集
    • 详细代码和注释

并查集的思想与介绍

可以查看这片文章!

https://blog.csdn.net/weixin_42305672/article/details/112699785

力扣721.账户合并

题目描述

给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该账户的邮箱地址。

现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。

合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是按字符 ASCII 顺序排列的邮箱地址。账户本身可以以任意顺序返回。

输入:
accounts = [[“John”, “[email protected]”, “[email protected]”], [“John”, “[email protected]”], [“John”, “[email protected]”, “[email protected]”], [“Mary”, “[email protected]”]]
输出:
[[“John”, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’], [“John”, “[email protected]”], [“Mary”, “[email protected]”]]
解释:
第一个和第三个 John 是同一个人,因为他们有共同的邮箱地址 “[email protected]”。
第二个 John 和 Mary 是不同的人,因为他们的邮箱地址没有被其他帐户使用。
可以以任何顺序返回这些列表,例如答案 [[‘Mary’,‘[email protected]’],[‘John’,‘[email protected]’],
[‘John’,‘[email protected]’,‘[email protected]’,‘[email protected]’]] 也是正确的。

题目解读 方法:哈希表+并查集

两个账户需要合并,当且仅当两个账户至少有一个共同的邮箱地址,因此这道题的实质是判断所有的邮箱地址中有哪些邮箱地址必定属于同一人,可以使用并查集实现。

为了使用并查集实现账户合并,需要知道一共有多少个不同的邮箱地址,以及每个邮箱对应的名称,因此需要使用两个哈希表分别记录每个邮箱对应的编号和每个邮箱对应的名称,遍历所有的账户并在两个哈希表中记录相应的信息。虽然同一个邮箱地址可能在多个账户中出现,但是同一个邮箱地址在两个哈希表中都只能存储一次。

然后使用并查集进行合并操作。由于同一个账户中的邮箱地址一定属于同一个人,因此遍历每个账户,对账户中的邮箱地址进行合并操作。并查集存储的是每个邮箱地址对应的编号,合并操作也是针对编号进行合并。

完成并查集的合并操作之后,即可知道合并后有多少个不同的账户。遍历所有的邮箱地址,对于每个邮箱地址,通过并查集得到该邮箱地址属于哪个合并后的账户,即可整理出每个合并后的账户包含哪些邮箱地址。

对于每个合并后的账户,需要整理出题目要求的返回账户的格式,具体做法是:将邮箱地址排序,账户的名称可以通过在哈希表中查找任意一个邮箱对应的名称得到,将名称和排序后的邮箱地址整理成一个账户列表。对所有合并后的账户整理出账户列表,即可得到最终答案。

详细代码和注释

class Solution {
    public List<List<String>> accountsMerge(List<List<String>> accounts) {
        Map<String, Integer> emailToIndex = new HashMap<String, Integer>();
        Map<String, String> emailToName = new HashMap<String, String>();
        int emailsCount = 0;
        //遍历数字,生成两个hashmap表,一个对应email-》编号,一个对应email-》名称(人)
        for (List<String> account : accounts) {
            String name = account.get(0);
            int size = account.size();
            for (int i = 1; i < size; i++) {
                String email = account.get(i);
                if (!emailToIndex.containsKey(email)) {
                    emailToIndex.put(email, emailsCount++);
                    emailToName.put(email, name);
                }
            }
        }
        //将同一个用户下的邮箱全部合并,结束后每一个用户的邮箱都有其根节点。
        UnionFind uf = new UnionFind(emailsCount);
        for (List<String> account : accounts) {
            String firstEmail = account.get(1);//第一个邮箱地址
            int firstIndex = emailToIndex.get(firstEmail);//第一个邮箱地址对应的编号
            int size = account.size();//第一个用户的邮箱个数
            for (int i = 2; i < size; i++) {//遍历邮箱
                String nextEmail = account.get(i);//第i个邮箱
                int nextIndex = emailToIndex.get(nextEmail);
                uf.union(firstIndex, nextIndex);//合并编号
            }
        }

        //建立一个Index对应邮箱地址的Map
        Map<Integer, List<String>> indexToEmails = new HashMap<Integer, List<String>>();
        //通过emailToIndex遍历邮箱地址
        for (String email : emailToIndex.keySet()) {
            //根据索引在并查集中得到当前邮箱地址的根节点
            int index = uf.find(emailToIndex.get(email));
            //判断是否存在根节点,没有就创建一个ArrrayList,有的话就获取当前的ArrayList
            List<String> account = indexToEmails.getOrDefault(index, new ArrayList<String>());
            //在当前的List中加入现邮箱地址,因为他们有同一个根
            account.add(email);
            //更新indexToEmails的邮箱列表
            indexToEmails.put(index, account);
        }

        //每个账户的第一个元素是名称,其余元素是按顺序排列的邮箱地址
        List<List<String>> merged = new ArrayList<List<String>>();
        for (List<String> emails : indexToEmails.values()) {//每一组邮箱的值
            Collections.sort(emails);//对邮箱进行排列
            String name = emailToName.get(emails.get(0));//获取任意一个邮箱所对应的名称
            List<String> account = new ArrayList<String>();//创建空List
            account.add(name);//第一个位置放账户名称
            account.addAll(emails);
            merged.add(account);
        }
        return merged;
    }
}
//并查集类
class UnionFind {
    int[] parent;
    //并查集初始化
    public UnionFind(int n) {
        parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }
    //并查集合并
    public void union(int index1, int index2) {
        parent[find(index2)] = find(index1);
    }
    //并查集查找
    public int find(int index) {
        if (parent[index] != index) {
            parent[index] = find(parent[index]);
        }
        return parent[index];
    }
}

力扣839.相似字符串组

题目描述

如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。

例如,“tars” 和 “rats” 是相似的 (交换 0 与 2 的位置); “rats” 和 “arts” 也是相似的,但是 “star” 不与 “tars”,“rats”,或 “arts” 相似。

总之,它们通过相似性形成了两个关联组:{“tars”, “rats”, “arts”} 和 {“star”}。注意,“tars” 和 “arts” 是在同一组中,即使它们并不相似。形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。

给你一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个字母异位词。请问 strs 中有多少个相似字符串组?

题目解读 方法:并查集

本题主要是针对给出的一组字符串进行判断相似度,相似度的定义为:如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,那么称 X 和 Y 两个字符串相似。

我们把每一个字符串看作点,字符串之间是否相似看作边,那么可以发现本题询问的是给定的图中有多少连通分量。于是可以想到使用并查集维护节点间的连通性。

我们枚举给定序列中的任意一对字符串,检查其是否具有相似性,如果相似,那么我们就将这对字符串相连。

在实际代码中,我们可以首先判断当前这对字符串是否已经连通,如果没有连通,我们再检查它们是否具有相似性,可以优化一定的时间复杂度的常数。

详细代码和注释

class Solution {
    int[] f;

    public int numSimilarGroups(String[] strs) {
        int n = strs.length;//n表示字符串总数
        int m = strs[0].length();//m表示每一个字符串的长度
        f = new int[n];
        //并查集初始化
        for (int i = 0; i < n; i++) {
            f[i] = i;
        }
        //遍历字符串 
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int fi = find(i), fj = find(j);
                if (fi == fj) {//如果两个字符串属于同一个集合,继续下一次循环
                    continue;
                }
                //检查两个字符串是否相似,如果相似就合并
                if (check(strs[i], strs[j], m)) {
                    f[fi] = fj;
                }
            }
        }
        //遍历查找有几个相同集
        int ret = 0;
        for (int i = 0; i < n; i++) {
            if (f[i] == i) {
                ret++;
            }
        }
        return ret;
    }

    //查找根节点 x代表第几个字符串
    public int find(int x) {
        return f[x] == x ? x : (f[x] = find(f[x]));
    }
    //检查两个字符串是否相似 最为重要!!!
    public boolean check(String a, String b, int len) {
        int num = 0;
        for (int i = 0; i < len; i++) {
            if (a.charAt(i) != b.charAt(i)) {
                num++;
                if (num > 2) {//如果不想等的位置超过2两个,则这两个肯定是不相似
                    return false;
                }
            }
        }
        return true;
    }
}

记录时间:2021年1月31日

你可能感兴趣的:(Java学习记录,字符串,数据结构,java,并查集,力扣)