【状态压缩 并集查找 图论】2157. 字符串分组

news2024/12/24 9:18:39

本文涉及知识点

状态压缩 并集查找 图论

LeetCode2157. 字符串分组

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

  • words[0] 可以得到 words[1] (将 ‘a’ 替换为 ‘b’)和 words[2] (添加 ‘b’)。所以 words[0] 与 words[1] 和 words[2] 关联。
  • words[1] 可以得到 words[0] (将 ‘b’ 替换为 ‘a’)和 words[2] (添加 ‘a’)。所以 words[1] 与 words[0] 和 words[2] 关联。
  • words[2] 可以得到 words[0] (删去 ‘b’)和 words[1] (删去 ‘a’)。所以 words[2] 与 words[0] 和 words[1] 关联。
  • words[3] 与 words 中其他字符串都不关联。
    所以,words 可以分成 2 个组 [“a”,“b”,“ab”] 和 [“cde”] 。最大的组大小为 3 。
    示例 2:
    输入:words = [“a”,“ab”,“abc”]
    输出:[1,3]
    解释:
  • words[0] 与 words[1] 关联。
  • words[1] 与 words[0] 和 words[2] 关联。
  • words[2] 与 words[1] 关联。
    由于所有字符串与其他字符串都关联,所以它们全部在同一个组内。
    所以最大的组大小为 3 。
    提示:

1 <= words.length <= 2 * 104
1 <= words[i].length <= 26
words[i] 只包含小写英文字母。
words[i] 中每个字母最多只出现一次。

状态压缩

由于本题words[i] 只包括小写字母,且没有重复字母,我们可以对字母集合进行状态压缩, maks&(1<<j) 表示 字母集合存在 ‘a’+j 。
mMask,key 表示某个字母集合 value是一个vector 记录words[i]的下标i。key和words[i]关联。

然后通过 key value建立 并集查找。
注意
一,key 必须是words中存在的字母集合。
二,(mask | (1 << j1)) ^ ( 1 << j2) 括号不能省略
三,建立并集查找时,只需要v[i]和v[i-1]连接,不需要两两连接。前者时间复杂度是O(n),后者时间复杂度是O(nn)。

代码

核心代码

class CUnionFind
{
public:
	CUnionFind(int iSize) :m_vNodeToRegion(iSize)
	{
		for (int i = 0; i < iSize; i++)
		{
			m_vNodeToRegion[i] = i;
		}
		m_iConnetRegionCount = iSize;
	}	
	CUnionFind(vector<vector<int>>& vNeiBo):CUnionFind(vNeiBo.size())
	{
		for (int i = 0; i < vNeiBo.size(); i++) {
			for (const auto& n : vNeiBo[i]) {
				Union(i, n);
			}
		}
	}
	int GetConnectRegionIndex(int iNode)
	{
		int& iConnectNO = m_vNodeToRegion[iNode];
		if (iNode == iConnectNO)
		{
			return iNode;
		}
		return iConnectNO = GetConnectRegionIndex(iConnectNO);
	}
	void Union(int iNode1, int iNode2)
	{
		const int iConnectNO1 = GetConnectRegionIndex(iNode1);
		const int iConnectNO2 = GetConnectRegionIndex(iNode2);
		if (iConnectNO1 == iConnectNO2)
		{
			return;
		}
		m_iConnetRegionCount--;
		if (iConnectNO1 > iConnectNO2)
		{
			UnionConnect(iConnectNO1, iConnectNO2);
		}
		else
		{
			UnionConnect(iConnectNO2, iConnectNO1);
		}
	}

	bool IsConnect(int iNode1, int iNode2)
	{
		return GetConnectRegionIndex(iNode1) == GetConnectRegionIndex(iNode2);
	}
	int GetConnetRegionCount()const
	{
		return m_iConnetRegionCount;
	}
	vector<int> GetNodeCountOfRegion()//各联通区域的节点数量
	{
		const int iNodeSize = m_vNodeToRegion.size();
		vector<int> vRet(iNodeSize);
		for (int i = 0; i < iNodeSize; i++)
		{
			vRet[GetConnectRegionIndex(i)]++;
		}
		return vRet;
	}
	std::unordered_map<int, vector<int>> GetNodeOfRegion()
	{
		std::unordered_map<int, vector<int>> ret;
		const int iNodeSize = m_vNodeToRegion.size();
		for (int i = 0; i < iNodeSize; i++)
		{
			ret[GetConnectRegionIndex(i)].emplace_back(i);
		}
		return ret;
	}
private:
	void UnionConnect(int iFrom, int iTo)
	{
		m_vNodeToRegion[iFrom] = iTo;
	}
	vector<int> m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引
	int m_iConnetRegionCount;
};

class Solution {
public:
	vector<int> groupStrings(vector<string>& words) {
		unordered_map<int, vector<int>> mMask;
		for (int i = 0; i < words.size(); i++) {
			int iMask = 0;
			for (const auto& ch : words[i]) {
				iMask |= (1 << (ch - 'a'));
			}
			mMask[iMask].emplace_back(i);
		}		
		for (const auto& [mask, tmp] : mMask) {
			auto AddNewMask = [&](int mask,const int iMask) {
				if (!mMask.count(iMask)) { return; }
				mMask[mask].emplace_back(mMask[iMask][0]);
			};
			for (int j = 0; j < 26; j++) {
				if (mask & (1 << j)) {
					AddNewMask(mask, mask ^ (1 << j));
				}
				else {
					AddNewMask(mask, mask | (1 << j));
				}
			}
			for (int j1 = 0; j1 < 26; j1++) {
				if (mask & (1 << j1)) { continue; }
				for (int j2 = 0; j2 < 26; j2++) {
					if (!(mask & (1 << j2))) { continue; }
					AddNewMask(mask, (mask | (1 << j1)) ^ ( 1 << j2));
				}
			}
		}
		CUnionFind uf(words.size());
		for (const auto& [mask, v] : mMask) {
			for (int i = 1; i < v.size(); i++) {
				uf.Union(v[i], v[i - 1]);
			}
		}
		auto m = uf.GetNodeOfRegion();
		vector<int> vRet = { (int)m.size() };
		int iMax = 0;
		for (const auto& [tmp, v] : m) {
			iMax = max(iMax, (int)v.size());
		}
		vRet.emplace_back(iMax);
		return vRet;
	}
};

测试用例

class CUnionFind
{
public:
	CUnionFind(int iSize) :m_vNodeToRegion(iSize)
	{
		for (int i = 0; i < iSize; i++)
		{
			m_vNodeToRegion[i] = i;
		}
		m_iConnetRegionCount = iSize;
	}	
	CUnionFind(vector<vector<int>>& vNeiBo):CUnionFind(vNeiBo.size())
	{
		for (int i = 0; i < vNeiBo.size(); i++) {
			for (const auto& n : vNeiBo[i]) {
				Union(i, n);
			}
		}
	}
	int GetConnectRegionIndex(int iNode)
	{
		int& iConnectNO = m_vNodeToRegion[iNode];
		if (iNode == iConnectNO)
		{
			return iNode;
		}
		return iConnectNO = GetConnectRegionIndex(iConnectNO);
	}
	void Union(int iNode1, int iNode2)
	{
		const int iConnectNO1 = GetConnectRegionIndex(iNode1);
		const int iConnectNO2 = GetConnectRegionIndex(iNode2);
		if (iConnectNO1 == iConnectNO2)
		{
			return;
		}
		m_iConnetRegionCount--;
		if (iConnectNO1 > iConnectNO2)
		{
			UnionConnect(iConnectNO1, iConnectNO2);
		}
		else
		{
			UnionConnect(iConnectNO2, iConnectNO1);
		}
	}

	bool IsConnect(int iNode1, int iNode2)
	{
		return GetConnectRegionIndex(iNode1) == GetConnectRegionIndex(iNode2);
	}
	int GetConnetRegionCount()const
	{
		return m_iConnetRegionCount;
	}
	vector<int> GetNodeCountOfRegion()//各联通区域的节点数量
	{
		const int iNodeSize = m_vNodeToRegion.size();
		vector<int> vRet(iNodeSize);
		for (int i = 0; i < iNodeSize; i++)
		{
			vRet[GetConnectRegionIndex(i)]++;
		}
		return vRet;
	}
	std::unordered_map<int, vector<int>> GetNodeOfRegion()
	{
		std::unordered_map<int, vector<int>> ret;
		const int iNodeSize = m_vNodeToRegion.size();
		for (int i = 0; i < iNodeSize; i++)
		{
			ret[GetConnectRegionIndex(i)].emplace_back(i);
		}
		return ret;
	}
private:
	void UnionConnect(int iFrom, int iTo)
	{
		m_vNodeToRegion[iFrom] = iTo;
	}
	vector<int> m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引
	int m_iConnetRegionCount;
};

class Solution {
public:
	vector<int> groupStrings(vector<string>& words) {
		unordered_map<int, vector<int>> mMask;
		for (int i = 0; i < words.size(); i++) {
			int iMask = 0;
			for (const auto& ch : words[i]) {
				iMask |= (1 << (ch - 'a'));
			}
			mMask[iMask].emplace_back(i);
		}		
		for (const auto& [mask, tmp] : mMask) {
			auto AddNewMask = [&](int mask,const int iMask) {
				if (!mMask.count(iMask)) { return; }
				mMask[mask].emplace_back(mMask[iMask][0]);
			};
			for (int j = 0; j < 26; j++) {
				if (mask & (1 << j)) {
					AddNewMask(mask, mask ^ (1 << j));
				}
				else {
					AddNewMask(mask, mask | (1 << j));
				}
			}
			for (int j1 = 0; j1 < 26; j1++) {
				if (mask & (1 << j1)) { continue; }
				for (int j2 = 0; j2 < 26; j2++) {
					if (!(mask & (1 << j2))) { continue; }
					AddNewMask(mask, (mask | (1 << j1)) ^ ( 1 << j2));
				}
			}
		}
		CUnionFind uf(words.size());
		for (const auto& [mask, v] : mMask) {
			for (int i = 1; i < v.size(); i++) {
				uf.Union(v[i], v[i - 1]);
			}
		}
		auto m = uf.GetNodeOfRegion();
		vector<int> vRet = { (int)m.size() };
		int iMax = 0;
		for (const auto& [tmp, v] : m) {
			iMax = max(iMax, (int)v.size());
		}
		vRet.emplace_back(iMax);
		return vRet;
	}
};

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{

    assert(t1 == t2);
}

template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
    if (v1.size() != v2.size())
    {
        assert(false);
        return;
    }
    for (int i = 0; i < v1.size(); i++)
    {
        Assert(v1[i], v2[i]);
    }

}

int main()
{
	vector<string> words;
	{
		Solution sln;
		words = { "a", "b" };
		auto res = sln.groupStrings(words);
		Assert({ 1,2 }, res);
	}
	{
		Solution sln;
		words = { "a", "b", "ab", "cde" };
		auto res = sln.groupStrings(words);
		Assert({ 2,3 }, res);
	}
	{
		Solution sln;
		words = { "a","ab","abc" };
		auto res = sln.groupStrings(words);
		Assert({ 1,3 }, res);
	}
}

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1611455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQLite FTS3 和 FTS4 扩展(三十二)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite 的命令行 Shell(三十一&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 概述 FTS3 和 FTS4 是 SQLite 虚拟表模块&#xff0c;允许用户执行 对一组文档进行全文搜索。最常见&#xff08;和最有效…

csdn的编写教程(官方给的)

自定义的目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个注脚…

CCF-CSP真题《202312-3 树上搜索》思路+c++满分题解

想查看其他题的真题及题解的同学可以前往查看&#xff1a;CCF-CSP真题附题解大全 问题描述 试题编号&#xff1a;202312-3试题名称&#xff1a;树上搜索时间限制&#xff1a;1.0s内存限制&#xff1a;512.0MB问题描述&#xff1a; 题目背景 问题描述 输入格式 输出格式 样…

【Leetcode每日一题】 穷举vs暴搜vs深搜vs回溯vs剪枝_全排列 - 全排列(难度⭐⭐)(62)

1. 题目解析 题目链接&#xff1a;46. 全排列 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 回溯算法是一种通过探索所有可能的候选解来找出所有解的算法。当候选解被确认不是一个解&#xff08;或者至少不是最后一…

【QT进阶】Qt http编程之后端API测试工具postman使用介绍

往期回顾 【QT进阶】Qt Web混合编程之使用ECharts显示各类折线图等-CSDN博客 【QT进阶】Qt Web混合编程之实现ECharts数据交互动态修改-CSDN博客 【QT进阶】Qt http编程之http与https简单介绍-CSDN博客 【QT进阶】Qt http编程之后端API测试工具postman使用介绍 其实这个工具的…

springboot是什么?

可以应用于Web相关的应用开发。 选择合适的框架&#xff0c;去开发相关的功能&#xff0c;会有更高的效率。 为什么Spring Boot才是你该学的!学java找工作必会技能!在职程序员带你梳理JavaEE框架_哔哩哔哩_bilibili java工程师的必备技能 Spring是Java EE领域的企业级开发宽…

VSCode 目录折叠展开、缩进深度设置

1、VSCode 目录折叠展开设置 运行 Visual Studio Code &#xff0c;按 Ctrl &#xff0c;打开设置 输入Explorer:Compact Folders&#xff0c;取消勾选 或者在设置文件上添加 "explorer.compactFolders": false2、VSCode 目录缩进深度设置 输入Workbench Tree:…

计算机视觉——OpenCV Python位运算与图像掩码

概述 位运算与图像掩码的结合允许对图像的特定区域进行精确的操作。通过使用位运算&#xff08;如AND、OR、XOR和NOT&#xff09;&#xff0c;可以基于掩码的选择性地修改图像数据。位运算与图像掩码结合使用的一些关键点和应用场景&#xff1a; 选择性修改&#xff1a; 通过位…

视频质量评价 PSNR 算法详细介绍

PSNR PSNR(Peak Signal-to-Noise Ratio,峰值信噪比)是一种常用的评价图像质量的指标,尤其在图像压缩和图像处理领域。它基于最大可能的图像信号功率和图像的噪声功率之间的比率,通常用于衡量图像恢复或图像压缩算法的效果。 原理 PSNR是基于MSE(Mean Squared Error,均…

【网站项目】高校毕业论文管理系统小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

【java解决线程间变量不可见性的方案】

解决线程间变量不可见性的方案 一、 背景 所有的实例变量和类变量都存储在主内存&#xff0c;但每个线程都有自己的工作内存&#xff0c;保留了主内存的共享变量的副本&#xff0c;线程修改的是共享变量&#xff0c;但是每个线程每次只能读取工作内存里的值&#xff0c;所以会…

Flink窗口机制

1.窗口的概念 时间是为窗口服务的。窗口是什么&#xff1f;为什么会有窗口呢&#xff1f; &#xff08;1&#xff09;Flink要处理的数据&#xff0c;一般是从Kafka过来的流式数据&#xff0c;如果只是单纯地统计流的数据量&#xff0c;是没办法统计的。 &#xff08;2&#xff…

2024蓝桥杯每日一题(组合计数)

备战2024年蓝桥杯 -- 每日一题 Python大学A组 试题一&#xff1a;计算系数 试题二&#xff1a;求组合数1 试题三&#xff1a;求组合数2 试题四&#xff1a;杨辉三角形 试题一&#xff1a;计算系数 【题目描述】 给定一个多项式 (axby)k&#xff0c;请…

Google Earth Engine 洪水制图 - 使用 Sentinel-1 SAR GRD

Sentinel-1 提供从具有双极化功能的 C 波段合成孔径雷达 (SAR) 设备获得的信息。该数据包括地面范围检测 (GRD) 场景,这些场景已通过 Sentinel-1 工具箱进行处理,以创建经过校准和正射校正的产品。该集合每天都会更新,新获得的资产会在可用后两天内添加。 该集合包含所有 G…

【研发管理】产品经理知识体系-产品创新管理

导读&#xff1a; 产品创新管理对企业的发展具有深远的影响&#xff0c;它不仅是企业保持竞争优势的关键&#xff0c;也是推动企业持续稳定发展的重要动力。因此&#xff0c;企业应高度重视产品创新管理&#xff0c;并采取有效的策略和方法来推动产品创新活动的开展。对于产品经…

.NET 邮件发送 SMTP邮件发送

SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;是用于电子邮件传输的规则集&#xff0c;可以从邮件客户端向接收电子邮件服务器发送、中继或转发邮件。发件人可使用SMTP 服务器来执行发送电子邮件的过程。SMTP服务器则是按照这些规则中转电子邮件的服务器。 IMAP…

Java web应用性能分析之【MySQL安装注意事项】

本文主要是针对以前LAMP&#xff0c;以及默认用apt安装的mysql。数据文件、日志文件都在一起&#xff1b;innodb_buffer_pool默认用128M。如果你排查问题&#xff0c;最后发现是因为mysql的安装配置不对&#xff0c;是否一口老血要喷出来。同时给MySQL数据库安装做参考。 关于M…

Docker - HelloWorld

原文地址&#xff0c;使用效果更佳&#xff01; Docker - HelloWorld | CoderMast编程桅杆https://www.codermast.com/dev-tools/docker/docker-helloworld.html 开始之前 在学习本小节之前&#xff0c;你必须确保你正确安装了 Docker&#xff0c;正确安装 Docker 是后续学习的…

【树莓派学习】hello,world!

系统安装及环境配置详见【树莓派学习】系统烧录及VNC连接、文件传输-CSDN博客 树莓派内置python3&#xff0c;可以直接利用python输出。

linux中如何挂载yum云仓库进行软件的安装

1.首先在根目录下建立文件&#xff0c;用来挂载镜像文件 [rootclient ~]# mkdir /rhel9 2.挂载镜像文件&#xff1a; [rootclient ~]# mount /dev/cdrom /rhel9 3.切换到 /etc/yum.repos.d 下的目录并查看 &#xff0c;创建 rhel9.repo文件&#xff0c;并编辑云仓库域名&am…