839. 相似字符串组
如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。
例如,“tars” 和 “rats” 是相似的 (交换 0 与 2 的位置); “rats” 和 “arts” 也是相似的,但是 “star” 不与 “tars”,“rats”,或 “arts” 相似。
总之,它们通过相似性形成了两个关联组:{“tars”, “rats”, “arts”} 和 {“star”}。注意,“tars” 和 “arts” 是在同一组中,即使它们并不相似。形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。
给你一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个字母异位词。请问 strs 中有多少个相似字符串组?
示例 1:
输入:strs = ["tars","rats","arts","star"]
输出:2
示例 2:
输入:strs = ["omv","ovm"]
输出:1
提示:
1 <= strs.length <= 300
1 <= strs[i].length <= 300
strs[i] 只包含小写字母。
strs 中的所有单词都具有相同的长度,且是彼此的字母异位词。
备注:
字母异位词(anagram),一种把某个字符串的字母的位置(顺序)加以改换所形成的新词。
题解:
方法一:并查集(转换矩阵)
此题明显是一个并查集问题
,关于并查集的问题我们可以通过这一博客进行加深了解并查集—省份数量。确认为并查集
后,我们可以直接套用并查集
模板,即使这是一道困难题其实在你掌握了并查集
的思想后也是极其简单的。
- 为了能够更好理解,我们可以先将字符串数组
strs
转换为矩阵
,就像省份数量一题一样,我们先得到一个表示各个字符串之间是否存在连接关系
的矩阵matrix
,接着初始化并查集
,遍历存储连接关系的矩阵matrix
,即可将题目给定的strs
字符串数组变成一个已经具有连接关系的并查集
。最后的操作自然是遍历该并查集
,查找其有几个部分即可。
代码:
class Solution {
public int numSimilarGroups(String[] strs) {
int[] par = new int[strs.length];
for(int i=0;i<par.length;i++){
par[i] = i; //先让每个字符串各自为一个集体
}
int res = 0;
int[][] matrix = new int[strs.length][strs.length];
check(matrix,strs); //将字符串数组转换为整型数组,即转换为关系矩阵
//遍历关系矩阵
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[0].length;j++){
if(matrix[i][j]==1){ //为1则代表i,j代表的字符串之间有连接关系
union(i,j,par); //并查集内部对该两个模块进行连接
}
}
}
for(int i=0;i<par.length;i++){ //遍历并查集
if(par[i]==i){ //并查集内部有好几个群体,我们只要知道群体的种类数量即可
res++; //这里就是通过只看每个群体的老大,看共有几个老大来判断有几个群体
}
}
return res;
}
//将字符串数组转换为关系矩阵
public void check(int[][] matrix,String[] strs){
for(int i=0;i<strs.length;i++){
for(int j=0;j<strs.length;j++){
if(checkcheck(strs[i],strs[j])){
matrix[i][j] = 1;
}
}
}
}
//检查两个字符串之间是否具有连接关系,即这两个字符串是否为相似字符串
public boolean checkcheck(String s1,String s2){
int dif = 0;
for(int i=0;i<s1.length();i++){
if(s1.charAt(i)!=s2.charAt(i)){
dif++;
}
}
if(dif==0 || dif==2){
return true;
}
return false;
}
//并查集union函数
public void union(int x,int y,int[] par){
int tx = find(x,par);
int ty = find(y,par);
if(tx!=ty){
par[tx] = par[ty];
}
}
//并查集find函数
public int find(int x,int[] par){
while(par[x]!=x){
x = par[x];
}
return x;
}
}
方法二:并查集优化(思维转换)
- 通过进一步思考我们发现不用将
字符串数组
转换为关系矩阵
,因为字符串数组
的下标完全可以当成我们的标识,即字符串数组的下标可以直接当成关系矩阵matrix[i][j]的i和j,因此我们可以直接对字符串数组当成关系矩阵进行操作,这里checkcheck(strs[i],strs[j])
就相当于前面判断matrix[i][j]==1
的操作。 - 并且由于这里在
checkcheck()函数
代码层面,我们使用了三层for循环
,因此时间复杂度
巨大,所以我们在checkcheck
函数中能多省几次遍历就多省几次,这样在套上外层的两层for
后就会快好多。
代码:
class Solution {
public int numSimilarGroups(String[] strs) {
int[] par = new int[strs.length];
for(int i=0;i<par.length;i++){
par[i] = i;
}
int res = 0;
for(int i=0;i<strs.length;i++){
for(int j=0;j<strs.length;j++){
if(checkcheck(strs[i],strs[j])){
union(i,j,par);
}
}
}
for(int i=0;i<par.length;i++){
if(par[i]==i){
res++;
}
}
return res;
}
public boolean checkcheck(String s1,String s2){
int dif = 0;
for(int i=0;i<s1.length();i++){
if(s1.charAt(i)!=s2.charAt(i)){
dif++;
}
if(dif>2){
return false;
}
}
return true;
}
public void union(int x,int y,int[] par){
int tx = find(x,par);
int ty = find(y,par);
if(tx!=ty){
par[tx] = par[ty];
}
}
public int find(int x,int[] par){
while(par[x]!=x){
x = par[x];
}
return x;
}
}