LeetCode算法题117解 + 并查集

一、题目复述

leetCode 算法题目:
https://leetcode-cn.com/problems/H6lPxb/
简单描述为:
1、提供一字符串数组,数组字符串元素之间是字母异位词 。例如"aaabb" 与"baaab"; 即,组成字符串的字母相同,并且字母出现的次数相同。字母位置可以不同。
2、定义两个字符串相似:一个字符串,至多交换一次字母的位置后,可以变成另一个字符串,则两个字符串相似。例如 “ab” 与 “ba”; “wbw” 与"bww";“stars” 与 “tsars”;
3、给定一个字符串列表 strs。满足条件1;请问 strs 中有多少个相似字符串组?

二、自己的题解

首先最先想到的是写一个方法,判断两个字符串是否相似,比较简单,直接给代码:

	@Test
    public void test() {
        System.out.println(isSimilarString("tars", "rats"));
        System.out.println(isSimilarString("tars", "star"));
    }

    private boolean isSimilarString(String str1, String str2) {
        if (str1.length() != str2.length()) {
            return false;
        }
        List<Integer> diffIndexList = new ArrayList<>();
        for (int i = 0; i < str1.length(); i++) {
            if (str1.charAt(i) != str2.charAt(i)) {
                diffIndexList.add(i);
                if (diffIndexList.size() > 2) {
                    return false;
                }
            }
        }
        if (diffIndexList.size() == 0) {
            return true;
        }
        if (diffIndexList.size() == 2) {
            return str1.charAt(diffIndexList.get(0)) == str2.charAt(diffIndexList.get(1)) && str1.charAt(diffIndexList.get(1)) == str2.charAt(diffIndexList.get(0));
        }
        return false;
    }

后面的代码先给思路,大家可以试试:
遍历字符串数组,先 Str0 与 Str1 比较相似,如果相似,则两个字符串放到一个集合里面。如果不相似,则两个字符串各自放到一个集合里面。Str2 依次与前面的集合比较,如果遇到集合的元素与 Str2 相似,则把 Str2 加入这个已经存在的集合。否则,Str2 自创一个集合。

public int numSimilarGroups(String[] strs) {
        List<List<String>> group = new ArrayList<>();
        for (int i = 0; i < strs.length; i++) {
            String str = strs[i];
            boolean addFlag = false;
            groupLoop:
            for (List<String> list : group) {
                for (String s : list) {
                    if (isSimilarString(s, str)) {
                        list.add(str);
                        addFlag = true;
                        break groupLoop;
                    }
                }
            }
            if (!addFlag) {
                List<String> listObj = new ArrayList<>();
                listObj.add(str);
                group.add(listObj);
            }
        }
        return group.size();
    }

然后简单测试,就提交了,结果报错,报错提示案例为:
在这里插入图片描述
想半天,没有思路的时候,反复回去读题,理解题目:“tars” 和 “rats” 是相似的 (交换 0 与 2 的位置); “rats” 和 “arts” 也是相似的,但是 “star” 不与 "tars"相似 简化为 A 和B相似,B和C相似,但是A和C不相似。这与数学中的相似是不同的。所以,可能遍历字符的时候,出现的是 A,C,B,A和C不相似,创建了两个集合,但是B与A,B与C相似,这样,遍历到B的时候,需要将 A,C所在的两个集合合并为一个相似的集合。其实我们可以将相似理解为连通,这样就符合我们的思路:
在这里插入图片描述
所以修改自己的代码:

	public int numSimilarGroups(String[] strs) {
        List<List<String>> group = new ArrayList<>();
        for (int i = 0; i < strs.length; i++) {
            String str = strs[i];
            List<Integer> similarListIndex = new ArrayList<>();
            groupLoop:
            for (int j = 0; j < group.size(); j++) {
                List<String> list = group.get(j);
                for (String s : list) {
                    if (isSimilarString(s, str)) {
                        similarListIndex.add(j);
                        break;
                    }
                }
            }
            if (similarListIndex.isEmpty()) {
                List<String> listObj = new ArrayList<>();
                listObj.add(str);
                group.add(listObj);
            } else if(similarListIndex.size() == 1) {
                group.get(similarListIndex.get(0)).add(str);
            } else {
                List<String> mergeList = new ArrayList<>();
                mergeList.add(str);
                for (Integer listIndex : similarListIndex) {
                    mergeList.addAll(group.get(listIndex));
                }
                int modify = 0;
                for (int listIndex : similarListIndex) {
                    group.remove(listIndex - modify);
                    modify++;
                }
                group.add(mergeList);
            }
        }
        return group.size();
    }

综合提交代码:https://leetcode-cn.com/problems/H6lPxb/

三、代码学习-并查集

做完之后,看看官方题解,没有很多解释,看了一个小时,稍稍懂了,补充代码的解释如下。顺便也学习了新知识——并查集。
别人的代码:

class Solution {
    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){
				// 遇到后面的元素与前面的元素相似的, 朴素的想法是,两个元素可以放一组了。组数可以减一了。但是:
				// ***难点*** 
				// 再,判断相似的元素是否有共同的祖先,
				// 如果祖先相同,例如 A与B相似,A是B的祖先;A与C相似,A是C的祖先;A已经把B,C拉到一个组了。遍历到B,C的时候,祖先都是A,就不用再减一了。他们的组数的影响已经记录过了。
				// 如果祖先不相同,就可以减一。
					//情况一:原始转态,祖先都是自己。
					//情况二:不同组的合并。例如 A与B 相似,A与C不相似,B与C相似;B的祖先是A,C的祖先是C,不相同,把C加入A的子孙。组数减一。 
                if(isSimilar(strs[i], strs[j]) && union(fathers, i, j)){
                    cnt--;
                }
            }
        }
        return cnt;
    }

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

    public boolean union(int[] fathers, int i, int j){
        int a = findFather(fathers, i);
        int b = findFather(fathers, j);
        if(a != b){
            fathers[a] = b; // 这里 fathers[b] = a; 也行,字符相似,谁做祖先都可以,只要保持统一,让后面比较的元素都做子孙就行。
            return true;
        }
        return false;
    }

    public int findFather(int[] fathers, int i){
        if(fathers[i] != i){
        	// 引用递归来求解。
            fathers[i] = findFather(fathers, fathers[i]);
        }
        return fathers[i];
    }
}

作者:SloanCheng
链接:https://leetcode-cn.com/problems/H6lPxb/solution/tu-bfshe-bing-cha-ji-liang-chong-fang-fa-hmof/
来源:力扣(LeetCode)
代码中所有注释为本文作者添加。
总结算法的核心思路是:
fathers[x] = y;
fathers[y] = y;
添加了一个fathers[]数组,来记录下标为X的元素与下标为Y的元素之间的关系。
设置数组关系的时候,引用递归来求解。

其他思考:这样的关系可以是相似,节点的连通性,菜单的父子关系,等等业务场景下使用。
优缺点分析:
引用的代码,效率高,直接求结果,给出相似的有几组,但是具体哪些字符在一个组是不知道的。业务场景下,有时候我们可能希望知道,有相同关系的一组元素具体有哪几个。这个时候,用我的第一种方法,只要查看group集合就可以知道。
并查集还有更丰富的知识,自行百度。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值