「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」
题目
给你一个下标从 开始的字符串数组 。每个字符串都只包含 小写英文字母 。 中任意一个子串中,每个字母都至多只出现一次。
如果通过以下操作之一,我们可以从 的字母集合得到 的字母集合,那么我们称这两个字符串为 关联的:
-
往 的字母集合中添加一个字母。
-
从 的字母集合中删去一个字母。
-
将 中的一个字母替换成另外任意一个字母(也可以替换为这个字母本身)。 数组 可以分为一个或者多个无交集的组 $。一个字符串与一个组如果满足以下 任一条件,它就属于这个组:
-
它与组内
至少
一个其他字符串关联。 -
它是这个组中
唯一
的字符串。 -
注意,你需要确保分好组后,一个组内的任一字符串与其他组的字符串都不关联。可以证明在这个条件下,分组方案是唯一的。
请你返回一个长度为 的数组 :
- 是 分组后的
总组数
。 - 是字符串数目最多的组所包含的字符串数目。
示例 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 。
题解
位运算 + 哈希 + 并查集
为什么要用位运算?已理解可以忽略本段
每个字符串可能需要添加、删除、修改字符;比如对 这个字符串添加一个字符,删除一个字符或者修改一个字符,如果仅通过字符串操作,想想就比较复杂。
简化删除、添加、修改流程,将字符串转换成 26 位的二进制:
对应关系
举个例子: 字符串从左到右,二进制是从右到左;这个要理解一下 通过位运算枚举 可以得到字符串转换出的值,将值放入哈希表 中
枚举 ,因为此时的 中存储的值已经代表原数组中字符串,这点不难理解
得到 ,需要字符串做删除、添加、修改操作;这里依然需要位运算
对于任意长度大于 1 的字符串转换成的二进制
在 区间枚举该二进制,二进制任意位位置 ,
- 如果 位是 ,将此位置设置为 ,表示删除操作
- 如果 位是 ,将此位置设置为 ,表示添加操作
- 如果 位是 ,将此位置设置为 ,并将其他为的位置其中一位置为 ,表示替换
- 通过添加、删除、修改后的值确认是否存在在 中
- 如果存在,将此时的值与添加、删除、修改后的值通过并查集合并
- 如果不存在,不做处理
处理完成后枚举并查集数据,返回并查集存在的分组数和最大分组数量
比较抱歉,作者暂时只有这一个思路,提交leetcode执行时间大约需要 ,时间复杂度还是挺高的。
代码
var groupStrings = function (words) {
const map = new Map()
const node = {}
const keyMap = {}
const len = words.length
const heightMap = {}
for (let i = 0; i < len; i++) {
const k = stringToNumber(words[i])
map.set(k, k)
keyMap[words[i]] = k
node[k] = k
heightMap[k] = 0
}
function merge(a, b) {
const x = find(a)
const y = find(b)
if (x === y) return
if (heightMap[x] == heightMap[y]) {
heightMap[x] = heightMap[x] + 1 //合并,树的高度加一
node[y] = x
} else {
if (heightMap[x] < heightMap[y]) {
node[x] = y
} else {
node[y] = x
}
}
}
function find(x) {
if (x === node[x]) return x
node[x] = find(node[x])
return node[x]
}
map.forEach((c) => {
// 这里需要位运算,处理增加,删除
for (let j = 0; j < 26; j++) {
const v = 1 << j
// 需要将26位二进制0变为1,1变为0;
const sign = c & (1 << j)
let k = c - (sign !== 0 ? v : -v)
if (k && map.has(k)) {
merge(c, map.get(k))
}
}
// 处理修改的,
for (let j = 0; j < 26; j++) {
const v = 1 << j
// 找到1,并把它删除
const t = c & v
if (t !== 0) {
// 找0并赋值为1
for (let k = 0; k < 26; k++) {
const t2 = c & (1 << k)
if (t2 === 0) {
const key = c + (1 << k) - v
if (key && map.has(key)) {
merge(c, map.get(key))
}
}
}
}
}
})
let rootMap = {}
let max = 0
for (let i = 0; i < len; i++) {
const k = keyMap[words[i]]
const root = find(node[k])
rootMap[root] = (rootMap[root] || 0) + 1
max = Math.max(max, rootMap[root])
}
let l = Object.keys(rootMap).length
return [l, max]
function stringToNumber(s) {
let n = 0
for (let i = 0; i < s.length; i++) {
const t = s[i].charCodeAt() - 97
n = n + (1 << t)
}
return n
}
}