题目难度: 简单
原题链接
题目描述
给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。
回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。
回文串不一定是字典当中的单词。
示例 1:
输入:"tactcoa" 输出:true(排列有"tacocat"、"atcocta",等等)
题目思考
- 什么字符串是回文串的排列?
- 能否做到常数空间复杂度?
解决方案
思路
- 分析题目,输入不需要是回文串,只需要其某种排列满足回文串即可,所以我们可以从回文串的性质入手
- 回文串有两种形式:一种是中间单独一个字符,两边对称;还有一种是最中间两个字符也对称。
- 也就是说,回文串中最多只有一个字符的出现次数为 1,其他都必须为偶数
- 所以我们可以使用一个计数字典来统计是否满足上述要求即可
- 以上就是核心的思路,接下来我们考虑如何优化空间。这里我们无需知道精确的次数,而是只需要知道奇偶性,所以我们可以利用异或操作,这样就只需要一个变量而不需要字典了。具体做法如下:
- 使用一个 mask + 字符的 ASCII 码来统计奇偶
- 每次遍历一个字符就把对应位取异或
- 这样最后如果 mask 上最多只有一个 1,则说明最多只有一个奇数次数的字符,满足题意
- 这里判断是否最多有一个 1 可以复用统计 1 的个数的方法,即
mask & (mask-1)
:如果它是 0,则说明要么 mask 本身是 0;要么 mask 是 2 的幂,一定只有一个 1
复杂度
- 时间复杂度 O(N): 需要遍历字符串一遍
- 空间复杂度: 使用计数字典 - O(N);使用位运算 - O(1)
代码
方案 1 - 计数字典
from collections import defaultdictclass Solution:def canPermutePalindrome(self, s: str) -> bool:# 方法1: 计数字典, 最多只能有一个字符计数为奇数d = defaultdict(int)for c in s:d[c] += 1# 判断次数为奇数的字符个数最多为1return sum(x & 1 for x in d.values()) <= 1
方案 2 - 异或位运算
class Solution:def canPermutePalindrome(self, s: str) -> bool:# 方法2: 位运算, 异或1<<ord, 最多只有一位是1才可以!mask = 0for c in s:mask ^= 1 << ord(c)return mask & (mask - 1) == 0