2023-05-23:如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,

那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。

例如,"tars" 和 "rats" 是相似的 (交换 0 与 2 的位置);

"rats" 和 "arts" 也是相似的,但是 "star" 不与 "tars","rats",或 "arts" 相似。

总之,它们通过相似性形成了两个关联组:{"tars", "rats", "arts"} 和 {"star"}。

注意,"tars" 和 "arts" 是在同一组中,即使它们并不相似。

形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。

给你一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个字母异位词。

请问 strs 中有多少个相似字符串组?

输入:strs = ["tars","rats","arts","star"]。

输出:2。

答案2023-05-23:

具体过程如下:

1.定义一个结构体 UnionFind,包含以下字段:

  • Father []int:每个元素的父节点;

  • Size []int:每个子集的大小;

  • Help []int:帮助数组;

  • Sets int:集合数量。

2.编写函数 NewUnionFind(n int) *UnionFind,创建一个新的并查集,需传入元素数量 n,实现如下:

  • 创建一个 UnionFind 结构体 uf,分别用 make 函数初始化父节点数组、子集大小数组和帮助数组,将集合数量 Sets 初始化为元素数量 n

  • 遍历每个元素,将其父节点初始化为自身,子集大小初始化为1。

  • 返回 uf

3.编写函数 Find(i int) int 实现路径压缩的查找操作,返回元素 i 所在集合的根节点,具体步骤如下:

  • 定义辅助变量 hi 为0;

  • 如果元素 i 的父节点不是它本身,将 i 加入帮助数组,将 i 更新为其父节点;

  • i 的父节点等于它本身时,表明已经到达集合的根节点,遍历帮助数组,依次将这些元素的父节点更新为根节点;

  • 返回根节点。

4.编写函数 Union(i, j int) 实现按秩合并的操作,将元素 i 所在集合和元素 j 所在集合合并成一个集合,具体步骤如下:

  • 分别查找元素 i 和元素 j 所在集合的根节点,如果它们所在的集合已经相同,则不需要合并;

  • 否则,比较两个集合的大小,将小的集合合并到大的集合中,并更新父节点和子集大小,同时将集合数量减1。

5.编写函数 Sets0() int 返回当前并查集中集合的数量,直接返回结构体字段 Sets 的值即可。

6.编写函数 numSimilarGroups(strs []string) int,遍历每对字符串,如果它们属于不同的集合,判断它们是否相似,如果是相似的则将它们合并到同一个集合中,最终返回并查集中剩余的集合数量,具体步骤如下:

  • 创建一个新的并查集 uf,元素数量为输入字符串列表 strs 的长度;

  • 遍历输入字符串列表 strs,对于每一对字符串 s1s2,判断它们是否属于同一个集合,如果不是,则比较它们是否相似,如果是相似的,则将它们所在集合合并;

  • 返回并查集中集合的数量。

7.在 main 函数中,给定输入字符串列表 strs,调用 numSimilarGroups 函数计算相似字符串组的数量,并输出结果。

时间复杂度:在最坏情况下,需要枚举任意两个字符串进行比较,因此需要 $O(n^2m)$ 的时间复杂度,其中 $n$ 是字符串数组 strs 中字符串的数量,$m$ 是字符串的长度。并查集合并操作的时间复杂度为 $\alpha(n)$,其中 $\alpha(n)$ 是反阿克曼函数的某个很小的值,可以看作是常数级别的时间复杂度,因此对总时间复杂度的贡献可以忽略不计。因此,最终的时间复杂度为 $O(n^2m)$。

空间复杂度:主要由并查集所用的空间和额外的辅助变量所占用的空间构成。其中,并查集需要的空间是 $O(n)$,辅助变量 Help 需要的空间也是 $O(n)$,因此总的空间复杂度为 $O(n)$。

go语言完整代码如下:

package main

import "fmt"

func numSimilarGroups(strs []string) int {
n, m := len(strs), len(strs[0])
uf := NewUnionFind(n)
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
if uf.Find(i) != uf.Find(j) {
diff := 0
for k := 0; k < m && diff < 3; k++ {
if strs[i][k] != strs[j][k] {
diff++
}
}
if diff == 0 || diff == 2 {
uf.Union(i, j)
}
}
}
}
return uf.Sets0()
} type UnionFind struct {
Father []int
Size []int
Sets int
Help []int
} func NewUnionFind(n int) *UnionFind {
uf := &UnionFind{
Father: make([]int, n),
Size: make([]int, n),
Help: make([]int, n),
Sets: n,
}
for i := 0; i < n; i++ {
uf.Father[i] = i
uf.Size[i] = 1
}
return uf
} func (uf *UnionFind) Find(i int) int {
hi := 0
for i != uf.Father[i] {
uf.Help[hi] = i
hi++
i = uf.Father[i]
}
for hi > 0 {
hi--
uf.Father[uf.Help[hi]] = i
}
return i
} func (uf *UnionFind) Union(i, j int) {
fi, fj := uf.Find(i), uf.Find(j)
if fi != fj {
if uf.Size[fi] >= uf.Size[fj] {
uf.Father[fj] = fi
uf.Size[fi] += uf.Size[fj]
} else {
uf.Father[fi] = fj
uf.Size[fj] += uf.Size[fi]
}
uf.Sets--
}
} func (uf *UnionFind) Sets0() int {
return uf.Sets
} func main() {
strs := []string{"tars", "rats", "arts", "star"}
res := numSimilarGroups(strs)
fmt.Println(res)
}

rust完整代码如下:

fn main() {
let strs = vec![
"tars".to_string(),
"rats".to_string(),
"arts".to_string(),
"star".to_string(),
];
let res = num_similar_groups(strs);
println!("{}", res);
} fn num_similar_groups(strs: Vec<String>) -> i32 {
let n = strs.len();
let m = strs[0].len();
let mut uf = UnionFind::new(n);
for i in 0..n {
for j in i + 1..n {
// [i] [j]
if uf.find(i) != uf.find(j) {
let mut diff = 0;
for k in 0..m {
if strs[i].as_bytes()[k] != strs[j].as_bytes()[k] {
diff += 1;
}
if diff >= 3 {
break;
}
}
if diff == 0 || diff == 2 {
uf.union(i, j);
}
}
}
}
uf.sets() as i32
} struct UnionFind {
father: Vec<usize>,
size: Vec<i32>,
help: Vec<usize>, // 添加help字段
sets: usize,
} impl UnionFind {
fn new(n: usize) -> Self {
let mut father = vec![0; n];
let size = vec![1; n];
for i in 0..n {
father[i] = i;
}
Self {
father,
size,
help: vec![0; n], // 初始化help
sets: n,
}
} fn find(&mut self, i: usize) -> usize {
let mut hi = 0;
let mut j = i;
while j != self.father[j] {
self.help[hi] = j;
hi += 1;
j = self.father[j];
}
while hi > 0 {
hi -= 1;
self.father[self.help[hi]] = j;
}
j
} fn union(&mut self, i: usize, j: usize) {
let fi = self.find(i);
let fj = self.find(j);
if fi != fj {
if self.size[fi] >= self.size[fj] {
self.father[fj] = fi;
self.size[fi] += self.size[fj];
} else {
self.father[fi] = fj;
self.size[fj] += self.size[fi];
}
self.sets -= 1;
}
} fn sets(&self) -> usize {
self.sets
}
}

c语言完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h> typedef struct {
int* father;
int* size;
int* help;
int sets;
} UnionFind; UnionFind* newUnionFind(int n) {
UnionFind* uf = (UnionFind*)malloc(sizeof(UnionFind));
uf->father = (int*)malloc(sizeof(int) * n);
uf->size = (int*)malloc(sizeof(int) * n);
uf->help = (int*)malloc(sizeof(int) * n);
for (int i = 0; i < n; i++) {
uf->father[i] = i;
uf->size[i] = 1;
}
uf->sets = n;
return uf;
} int find(UnionFind* uf, int i) {
int hi = 0;
while (i != uf->father[i]) {
uf->help[hi++] = i;
i = uf->father[i];
}
while (hi != 0) {
hi--;
uf->father[uf->help[hi]] = i;
}
return i;
} void unionSet(UnionFind* uf, int i, int j) {
int fi = find(uf, i);
int fj = find(uf, j);
if (fi != fj) {
if (uf->size[fi] >= uf->size[fj]) {
uf->father[fj] = fi;
uf->size[fi] += uf->size[fj];
}
else {
uf->father[fi] = fj;
uf->size[fj] += uf->size[fi];
}
uf->sets--;
}
} int getSets(UnionFind* uf) {
return uf->sets;
} int numSimilarGroups(char** strs, int strsSize) {
int n = strsSize, m = strlen(strs[0]);
UnionFind* uf = newUnionFind(n);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (find(uf, i) != find(uf, j)) {
int diff = 0;
for (int k = 0; k < m && diff < 3; k++) {
if (strs[i][k] != strs[j][k]) {
diff++;
}
}
if (diff == 0 || diff == 2) {
unionSet(uf, i, j);
}
}
}
}
return getSets(uf);
} int main() {
char* strs[] = { "tars", "rats", "arts", "star" };
int strsSize = sizeof(strs) / sizeof(strs[0]);
int res = numSimilarGroups(strs, strsSize);
printf("%d\n", res); // 输出 2
return 0;
}

c++完整代码如下:

#include <iostream>
#include <vector>
using namespace std; class UnionFind {
public:
vector<int> father;
vector<int> size;
vector<int> help;
int sets; UnionFind(int n) : father(n), size(n, 1), help(n), sets(n) {
for (int i = 0; i < n; i++) {
father[i] = i;
}
} int find(int i) {
int hi = 0;
while (i != father[i]) {
help[hi++] = i;
i = father[i];
}
while (hi != 0) {
father[help[--hi]] = i;
}
return i;
} void unionSet(int i, int j) {
int fi = find(i);
int fj = find(j);
if (fi != fj) {
if (size[fi] >= size[fj]) {
father[fj] = fi;
size[fi] += size[fj];
}
else {
father[fi] = fj;
size[fj] += size[fi];
}
sets--;
}
} int getSets() {
return sets;
}
}; int numSimilarGroups(vector<string>& strs) {
int n = strs.size(), m = strs[0].size();
UnionFind uf(n);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (uf.find(i) != uf.find(j)) {
int diff = 0;
for (int k = 0; k < m && diff < 3; k++) {
if (strs[i][k] != strs[j][k]) {
diff++;
}
}
if (diff == 0 || diff == 2) {
uf.unionSet(i, j);
}
}
}
}
return uf.getSets();
} int main() {
vector<string> strs = { "tars", "rats", "arts", "star" };
int res = numSimilarGroups(strs);
cout << res << endl;
return 0;
}

2023-05-23:如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等, 那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。 例如,“tars“的更多相关文章

  1. C语言:将ss所指字符串中所有下标为奇数位置的字母转换为大写-将该字符串中的所有字符按ASCII码值升序排序后输出。-将a所指的4*3矩阵第k行的元素与第0行元素交换。

    //函数fun:将ss所指字符串中所有下标为奇数位置的字母转换为大写,若不是字母,则不转换. #include<conio.h> #include<stdio.h> #incl ...

  2. 字符串s中从第i个位置起取长度为len的子串,函数返回子串链表

    /*已知字符串采用带结点的链式存储结构(详见linksrting.h文件),请编写函数linkstring substring(linkstring s,int i,int len),在字符串s中从第 ...

  3. C#中使用IndexOf()判断字符串在字符串数组中第一次出现的索引位置

    ] {"}; "; //判断字符串的前几位在另一个字符串数组中第一次出现的索引位置 index = Array.IndexOf(s, s1.Substring(, ));

  4. 给定两个字符串 s 和 t,它们只包含小写字母。 字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。 请找出在 t 中被添加的字母。

    给定两个字符串 s 和 t,它们只包含小写字母.字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母.请找出在 t 中被添加的字母. 示例: 输入: s = "abcd" ...

  5. 字符串数组 输入3个字符串,要求按由小到大的字母顺序输出; 输入n个学生的姓名和学号到字符串数组中,在输入一个姓名,如果班级有该生则返回其信息,否则返回本班无此人

    输入3个字符串,要求按由小到大的字母顺序输出 如 输入franch england china,输出结果是china england franch 三个数排序输出,比较三个数的大小怎么做? a=18 ...

  6. Javascript ----字符串(String)中的方法

    涉及字符串时,常用到的几个方法... --------------------------------------------------------------------------------- ...

  7. c# 获取字符串数组中最长的的字符串并输出最长的字符串

    求字符串数组中最大长度的字符串: 实质就是比较字符串的长度: 方案一: class Program { static void Main(string[] args) { string[] array ...

  8. VBA中字符串连接/字符串拼接中“&”和“+”的区别

    VBA中字符串连接/字符串拼接中“&”和“+”的区别   在VBA中用于字符串连接的只有“&”和“+”两种运算符. 1.“&”是强制性连接,就是不管什么都连接. 2.“+”是对 ...

  9. 字符串匹配算法(在字符串T中查找是否有与字符串P相同的子串)

    T称为目标串(Target)或主串 ,P称为模式串(Pattren) 或子串 1. 简单字符串模式匹配算法 原理:用字符串P的字符依次与字符串T中的字符进行比较,首先将字符串P从第0个位置起与主串T的 ...

  10. Js中的字符串/数组中常用的操作

    JS为每种数据类型都内置很多方法,真的不好记忆,而且有些还容易记混,现整理如下,以便以后查看: 一.String ①charAt()方法用于返回指定索引处的字符.返回的字符是长度为 1 的字符串. 语 ...

随机推荐

  1. SpringBoot笔记--文件配置加载顺序+整合其他框架

    内部文件配置加载顺序 外部文件配置加载顺序 jar包配置 整合Junit 若是业务管理类和测试类在同一个包下面,那么这句话, 可以不加括号,只写注解名称 否则,就必须指定到包下面,不然会报错 整合Re ...

  2. Jetson Xavier NX 试玩 (二)

    Jetson Xavier NX 试玩 (二) Hello AI World Inference 人工智能推理模型 0 前言 想玩一玩 jetson 的人工智能功能,官方的 instructional ...

  3. Django-5

    Django-5 1.Coookie 1.1 什么是cookie Cookie是储存在浏览器端的一小段文本数据(键值对). 被广泛用于在网站之间传输信息, 当您访问一个网站时,它会将一个Cookie发 ...

  4. while与do-while的区别是什么,怎么用?

    前言 在上一篇文章中,壹哥给大家讲解了循环的概念,并重点给大家讲解了for循环的使用.但在Java中,除了for循环之外,还有while.do-while.foreach等循环形式.今天壹哥就再用一篇 ...

  5. CAS 单点登录系统

    一.什么是单点登录 单点登录(Sign Sion On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一.SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系 ...

  6. Java面试——Netty

    一.BIO.NIO 和 AIO [1]阻塞 IO(Blocking I/O):同步阻塞I/O模式,当一条线程执行 read() 或者 write() 方法时,这条线程会一直阻塞直到读取一些数据或者写出 ...

  7. JS一切皆对象理解

    对象都是通过函数创建的 function Fn() { this.name = '王福朋'; this.year = 1988; } var fn1 = new Fn(); fn1是个对象,它是由函数 ...

  8. mysql的concat与concat_ws拼接字符串的使用

    concat的使用 可以拼接多个字符 mysql> select concat(name,dept,job) from t1; +-----------------------+ | conca ...

  9. Binder机制及底层实现

    <1>进程间的内存空间是进程私有的<2>进程间和内核的空间是互通的<3>进程1空间<--->内核空间<-->进程2空间Binder跨进程通信 ...

  10. React框架使用

    一:使用Vite创建React项目 二:React中组件使用 import React, { Component, useState } from "react"; //使用cla ...