LeetCode 2157. 字符串分组

本文介绍了如何使用二进制压缩和图的连通块搜索算法解决LeetCode上的2157题——字符串分组。通过将字符串转化为26位二进制掩码,并建立哈希映射,然后进行深度优先搜索染色,找出所有关联的字符串,最终统计分组数量和最大组的大小。
摘要由CSDN通过智能技术生成

一、题目

1、题目描述

  给你一个下标从 0 0 0 开始的字符串数组 w o r d s words words。每个字符串都只包含小写英文字母 。 w o r d s words words 中任意一个子串中,每个字母都至多只出现一次。如果通过以下操作之一,我们可以从 s 1 s1 s1 的字母集合得到 s 2 s2 s2 的字母集合,那么我们称这两个字符串为 关联的 :
  1、往 s1 的字母集合中添加一个字母。
  2、从 s1 的字母集合中删去一个字母。
  3、将 s1 中的一个字母替换成另外任意一个字母(也可以替换为这个字母本身)。
  数组 w o r d s words words 可以分为一个或者多个无交集的组 。如果一个字符串与另一个字符串关联,那么它们应当属于同一个组。注意,你需要确保分好组后,一个组内的任一字符串与其他组的字符串都不关联。可以证明在这个条件下,分组方案是唯一的。请你返回一个长度为 2 的数组 ans :
  ans[0] 是 words 分组后的 总组数 。
  ans[1] 是字符串数目最多的组所包含的字符串数目。
  样例输入: ["a","b","ab","cde"]
  样例输出: [2,3]

2、基础框架

  • C语言 版本给出的基础框架代码如下:
int* groupStrings(char ** words, int wordsSize, int* returnSize){
}

3、原题链接

LeetCode 2157. 字符串分组

二、解题报告

1、思路分析

   ( 1 ) (1) (1) 容易得知,一旦产生关联,一定是双向关联。比如 a a a 可以删除一个字符后,变成 b b b;那么 b b b 就可以通过 添加一个字符变成 a a a。同样的,替换也是满足这个定律的。所以只要 a a a 关联 b b b b b b 就一定关联 a a a。说明这是一个图的双向边。
   ( 2 ) (2) (2) 由于每个字符串的字母都是唯一的,所以我们可以用 0 和 1 来表示这个字符串中对应字母中在不在,用一个 26 位的二进制数表示每个字符串,由于数字较大,所以需要用哈希映射到较小的范围区间内。
   ( 3 ) (3) (3) 首先对于每个字符串,我们先把它变成一个掩码mask,可以通过左移和位或来实现。如下代码表示,对于第 i i i 个字符,生成它的掩码。

     mask = 0;
     for(j = 0; j < words[i].size(); ++j) {
         mask |= (1 << (words[i][j] - 'a'));
     }

   ( 4 ) (4) (4) 用一个哈希表id标记mask对应的唯一标识。并且用cnt[ID]数组来记录对应的结点的数目。id2Mask则代表的是id这个哈希表的反向映射。
  更加详细的,id是键为mask值为ID的哈希表,id2Mask是值为mask键为ID哈希表。两者互为反射。

	unordered_map< int, int > id;
    if(id.find( mask ) == id.end()) {
        id[ mask ] = ++ID;
        cnt[ ID ] = 1;
        id2Mask[ ID ] = mask;
    }else {
        cnt[ id[ mask ] ]++;
    }

   ( 5 ) (5) (5) 对于每一个 mask对应的 ID进行搜索找连通块并且染色。
   ( 6 ) (6) (6) 染色的过程,扩展结点可以用深度优先搜索加二进制枚举,对于结点的扩展,只有三种情况。
     ( 6.1 ) (6.1) (6.1) 二进制位上去掉一个一;
     ( 6.2 ) (6.2) (6.2) 二进制位上加上一个一;
     ( 6.3 ) (6.3) (6.3) 二进制位上加上一个一,再去掉一个一;
   ( 7 ) (7) (7) 最后将染色完的结点进行分组统计即可。

2、时间复杂度

   O ( n ∗ 26 ∗ 26 ) O(n*26*26) O(n2626)

3、代码详解

#define maxn 20010

class Solution {

    
    int color[maxn], colorCnt;
    unordered_map< int, int > id;
    int id2Mask[maxn];
    int cnt[maxn];

    void dfs(int u, int c) {
        int x = id2Mask[u];
        int i, j;
        if(color[u] != -1) {
            return ;
        }
        color[u] = c;

        // 对于 id2Mask[u] 来说,只有三种情况:
        // 1. 二进制位上去掉一个1
        for(i = 0; i < 26; ++i) {
            if(x & (1<<i)) {
                int v = (x ^ (1<<i));
                if( id.find(v) != id.end() ) {
                    dfs( id[v], c );
                }
            }
        }


        // 2. 二进制位上加上一个1
        for(i = 0; i < 26; ++i) {
            if(x & (1<<i)) {
                continue;
            }else {
                int v = (x ^ (1<<i));
                if( id.find(v) != id.end() ) {
                    dfs( id[v], c );
                }
            }
        }


        // 3. 二进制位上去掉1个1,再在另一个位置上加上1个1
        for(i = 0; i < 26; ++i) {
            if( !(x & (1<<i)) ) {
                continue;
            }
            for(j = 0; j < 26; ++j) {
                if(i == j) {
                    continue;
                }else if( !(x & (1<<j)) ) {
                    int v = (x ^ (1<<i) ^ (1<<j));
                    if( id.find(v) != id.end() ) {
                        dfs( id[v], c );
                    }
                }
            }
        }
    }
public:
    vector<int> groupStrings(vector<string>& words) {
        int i, j;
        int mask;
        int ID = 0;
        int maxColor = -1;
        int hashColor[maxn];

        id.clear();
        memset(color, -1, sizeof(color));
        
        for(i = 0; i < words.size(); ++i) {
            mask = 0;
            for(j = 0; j < words[i].size(); ++j) {
                mask |= (1 << (words[i][j] - 'a'));
            }
            if(id.find( mask ) == id.end()) {
                id[ mask ] = ++ID;
                cnt[ ID ] = 1;
                id2Mask[ ID ] = mask;
            }else {
                cnt[ id[ mask ] ]++;
            }
        }
        colorCnt = 0;
        // 1 -> ID
        for(i = 1; i <= ID; ++i) {
            if(-1 == color[i]) {
                dfs(i, colorCnt);
                ++colorCnt;
            }
        }

        memset(hashColor, 0, sizeof(hashColor));

        for(i = 1; i <= ID; ++i) {
           hashColor[ color[i] ] += cnt[i];
        }

        for(i = 0; i < colorCnt; ++i) {
            if(hashColor[i] > maxColor) {
                maxColor = hashColor[i];
            }
        }
        return {colorCnt, maxColor};
    }
};

三、本题小知识

  本题的切入点在于字母是集合,不是序列,所以可以采用二进制压缩。


四、加群须知

  相信看我文章的大多数都是「 大学生 」,能上大学的都是「 精英 」,那么我们自然要「 精益求精 」,如果你还是「 大一 」,那么太好了,你拥有大把时间,当然你可以选择「 刷剧 」,然而,「 学好算法 」,三年后的你自然「 不能同日而语 」
  那么这里,我整理了「 几十个基础算法 」 的分类,点击开启:

🌌《算法入门指引》🌌

  如果链接被屏蔽,或者有权限问题,可以私聊作者解决。

  大致题集一览:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述



在这里插入图片描述


  为了让这件事情变得有趣,以及「 照顾初学者 」,目前题目只开放最简单的算法 「 枚举系列 」 (包括:线性枚举、双指针、前缀和、二分枚举、三分枚举),当有 一半成员刷完 「 枚举系列 」 的所有题以后,会开放下个章节,等这套题全部刷完,你还在群里,那么你就会成为「 夜深人静写算法 」专家团 的一员。
  不要小看这个专家团,三年之后,你将会是别人 望尘莫及 的存在。如果要加入,可以联系我,考虑到大家都是学生, 没有「 主要经济来源 」,在你成为神的路上,「 不会索取任何 」
  🔥联系作者,或者扫作者主页二维码加群,加入刷题行列吧🔥


🔥让天下没有难学的算法🔥

C语言免费动漫教程,和我一起打卡!
🌞《光天化日学C语言》🌞

让你养成九天持续刷题的习惯
🔥《九日集训》🔥

入门级C语言真题汇总
🧡《C语言入门100例》🧡

组团学习,抱团生长
🌌《算法零基础100讲》🌌

几张动图学会一种数据结构
🌳《画解数据结构》🌳

竞赛选手金典图文教程
💜《夜深人静写算法》💜
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

英雄哪里出来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值