特殊等价字符串组

标签: 数组 哈希表 字符串 排序

难度: Medium

给你一个字符串数组 words

一步操作中,你可以交换字符串 words[i] 的任意两个偶数下标对应的字符或任意两个奇数下标对应的字符。

对两个字符串 words[i]words[j] 而言,如果经过任意次数的操作,words[i] == words[j] ,那么这两个字符串是 特殊等价 的。

  • 例如,words[i] = "zzxy"words[j] = "xyzz" 是一对 特殊等价 字符串,因为可以按 "zzxy" -> "xzzy" -> "xyzz" 的操作路径使 words[i] == words[j]

现在规定,words 一组特殊等价字符串 就是 words 的一个同时满足下述条件的非空子集:

  • 该组中的每一对字符串都是 特殊等价
  • 该组字符串已经涵盖了该类别中的所有特殊等价字符串,容量达到理论上的最大值(也就是说,如果一个字符串不在该组中,那么这个字符串就 不会 与该组内任何字符串特殊等价)

返回 words特殊等价字符串组 的数量。

示例 1:

输入:words = ["abcd","cdab","cbad","xyzz","zzxy","zzyx"]
输出:3
解释:
其中一组为 ["abcd", "cdab", "cbad"],因为它们是成对的特殊等价字符串,且没有其他字符串与这些字符串特殊等价。
另外两组分别是 ["xyzz", "zzxy"] 和 ["zzyx"]。特别需要注意的是,"zzxy" 不与 "zzyx" 特殊等价。

示例 2:

输入:words = ["abc","acb","bac","bca","cab","cba"]
输出:3
解释:3 组 ["abc","cba"],["acb","bca"],["bac","cab"]

提示:

  • 1 <= words.length <= 1000
  • 1 <= words[i].length <= 20
  • 所有 words[i] 都只由小写字母组成。
  • 所有 words[i] 都具有相同的长度。

Submission

运行时间: 23 ms

内存: 16.3 MB

class Solution:
    def numSpecialEquivGroups(self, words: List[str]) -> int:
        ans=set()
        for v in words:
            a,b=[],[]
            for i,p in enumerate(v):
                if i&1==0:
                    a.append(p)
                else:
                    b.append(p)
            a.sort()
            b.sort()
            ans.add("".join(a)+"#"+"".join(b))
        return len(ans)

Explain

此题解通过将每个字符串分成奇数和偶数索引的字符,并对这些字符分别排序,然后将排序后的字符重新组合为一个新的字符串,使用一个集合存储这些新字符串来确保唯一性。因为特殊等价的字符串在任意次操作后可以相互转换,其排序后的奇数和偶数索引字符序列将会相同。因此,通过比较这些排序后的序列可以判定两个字符串是否特殊等价。最终,集合中的元素数量即为特殊等价字符串组的数量。

时间复杂度: O(N * n log n)

空间复杂度: O(N * n)

class Solution:
    def numSpecialEquivGroups(self, words: List[str]) -> int:
        ans=set()
        for v in words:
            a,b=[],[]
            for i,p in enumerate(v):
                if i&1==0:  # 若索引i为偶数
                    a.append(p)
                else:  # 若索引i为奇数
                    b.append(p)
            a.sort()  # 对偶数索引的字符进行排序
            b.sort()  # 对奇数索引的字符进行排序
            ans.add("".join(a)+"#"+"".join(b))  # 将排序后的字符合并后添加到集合中,'#'作为分隔符
        return len(ans)  # 返回集合的元素数量,即特殊等价字符串组的数量

Explore

在此题解中,使用'#'作为分隔符是为了清晰地区分排序后的奇数索引字符数组和偶数索引字符数组,避免两个数组在合并时发生混淆。例如,字符数组['ab']和['cd']如果不使用分隔符直接合并成'abcd',将无法区分哪些字符属于奇数或偶数索引。使用'#'则明确分隔,如'ab#cd'。确实存在字符自身包含'#'的情况,这在不同的环境中可能引起混淆。然而,在此题解的语境中,通常假设输入字符串仅由小写英文字母组成,因此不考虑'#'字符的干扰。若要处理包含'#'或其他特殊字符的情况,可选择更加独特的分隔符,或使用其他非字符方式(例如元组)来存储两组字符数组。

通过排序来判断特殊等价的方法基本上是有效的,因为特殊等价的字符串通过重新排列偶数和奇数位置的字符可以互相转换,排序则是将这种转换归纳为统一形式。边界情况包括:1. 所有字符串都为空字符串的情况,这时候需要确保代码能正确处理空数组。2. 字符串长度为1的情况,此时只有偶数索引字符,奇数索引字符数组为空,需要确认合并时能正确处理。3. 输入字符串极为相似但排序后不同的情况,确保排序逻辑正确区分。在实际应用中,对性能的优化也可能是考虑点,尤其是在处理大数据量时。

该题解主要假设处理的是小写英文字母,但理论上这种方法可以应用于所有Unicode字符,因为Python的sort方法可以处理Unicode字符。然而,Unicode字符包括各种符号、表情等,这些可能在具体实现时需要特殊考虑。例如,某些Unicode字符可能在不同环境下有不同的排序行为或者需要考虑正规化问题(如组合字符)。在实际应用中,如果处理广泛的Unicode字符,可能需要考虑这些字符的特殊属性,如是否进行Unicode正规化来保证字符的一致性。