[路飞]_leetcode第278周竞赛(下)

407 阅读2分钟

「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战

2157. 字符串分组

题目

给你一个下标从 00 开始的字符串数组 wordswords 。每个字符串都只包含 小写英文字母 。wordswords 中任意一个子串中,每个字母都至多只出现一次。

如果通过以下操作之一,我们可以从s1s1 的字母集合得到 s2s2 的字母集合,那么我们称这两个字符串为 关联的:

  • s1s1 的字母集合中添加一个字母。

  • s1s1 的字母集合中删去一个字母。

  • s1s1 中的一个字母替换成另外任意一个字母(也可以替换为这个字母本身)。 数组 wordswords 可以分为一个或者多个无交集的组 $。一个字符串与一个组如果满足以下 任一条件,它就属于这个组:

  • 它与组内 至少 一个其他字符串关联。

  • 它是这个组中 唯一 的字符串。

  • 注意,你需要确保分好组后,一个组内的任一字符串与其他组的字符串都不关联。可以证明在这个条件下,分组方案是唯一的。

请你返回一个长度为 22 的数组 ansans

  • ans[0]ans[0] wordswords 分组后的 总组数
  • ans[1]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 。

题解

位运算 + 哈希 + 并查集

为什么要用位运算?已理解可以忽略本段

每个字符串可能需要添加、删除、修改字符;比如对 abcabc 这个字符串添加一个字符,删除一个字符或者修改一个字符,如果仅通过字符串操作,想想就比较复杂。

简化删除、添加、修改流程,将字符串转换成 26 位的二进制:

对应关系 abcd....xyzabcd....xyz
1111...1111111...111

举个例子: s=acd=>1101s = acd => 1101 字符串从左到右,二进制是从右到左;这个要理解一下 通过位运算枚举 wordswords 可以得到字符串转换出的值,将值放入哈希表 mapmap

枚举 mapmap,因为此时的 mapmap 中存储的值已经代表原数组中字符串,这点不难理解

得到 mapmap ,需要字符串做删除、添加、修改操作;这里依然需要位运算

对于任意长度大于 1 的字符串转换成的二进制
[0,26][0,26] 区间枚举该二进制,二进制任意位位置 kk,

  • 如果 kk 位是 11,将此位置设置为 00 ,表示删除操作
  • 如果 kk 位是 00,将此位置设置为 11 ,表示添加操作
  • 如果 kk 位是 00,将此位置设置为 11,并将其他为11的位置其中一位置为 00 ,表示替换
  • 通过添加、删除、修改后的值确认是否存在在 mapmap
    • 如果存在,将此时的值与添加、删除、修改后的值通过并查集合并
    • 如果不存在,不做处理

处理完成后枚举并查集数据,返回并查集存在的分组数和最大分组数量

比较抱歉,作者暂时只有这一个思路,提交leetcode执行时间大约需要 1000ms1000ms,时间复杂度还是挺高的。

代码

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
  }
}