LeetCode第249题:移位字符串分组
问题描述
给定一个字符串列表,将所有移位字符串分组,并返回这些组。
移位字符串指的是通过将字符串中的每个字母移动相同的位数(可以是正数或负数)所得到的新字符串。例如:把 “abc” 中的每个字母都向后移动 1 个位置,就得到了 “bcd”,这就是一次移位。
难度:中等
示例:
输入: ["abc", "bcd", "acef", "xyz", "az", "ba", "a", "z"]
输出:
[
["abc","bcd","xyz"],
["az","ba"],
["acef"],
["a","z"]
]
解释:
- "abc", "bcd", "xyz" 都可以由相同的移位值生成,因此它们在一组。
- "az", "ba" 都可以由相同的移位值生成。
- "acef" 无法与其他字符串通过相同的移位得到,所以它自成一组。
- "a", "z" 分别只包含一个字母,无论怎么移位都只是在它们自己,所以它们在一组。
注意:字母循环移动,例如 ‘z’ 向后移动 1 位会变成 ‘a’。
解题思路
要解决这个问题,我们需要找到一种方法来确定两个字符串是否可以通过相同的移位量得到彼此。
关键洞察
特征1: 如果两个字符串可以通过相同的移位量得到彼此,那么它们的长度必须相同。
特征2: 对于可以通过相同移位得到的字符串,它们相邻字符的ASCII差值是相同的。
特征3: 由于字母是循环移动的,我们需要对这个差值进行模26运算,确保结果在0到25之间。
解题步骤
- 对于每个字符串,计算其相邻字符的差值序列。
- 为了处理循环移动,对每个差值进行模26运算,确保结果在0到25之间。
- 对于长度为1的字符串,它们的差值序列是空的,可以单独处理(如将单字符字符串分为一组)。
- 使用哈希表保存差值序列相同的字符串组。
代码实现
C#实现
public class Solution {
public IList<IList<string>> GroupStrings(string[] strings) {
// 使用字典保存每一类移位字符串
var groups = new Dictionary<string, IList<string>>();
foreach (var s in strings) {
// 计算标准形式(移位序列)
string key = GetShiftSequence(s);
// 将当前字符串加入相应的组
if (!groups.ContainsKey(key)) {
groups[key] = new List<string>();
}
groups[key].Add(s);
}
// 返回所有分组
return groups.Values.ToList();
}
// 计算字符串的移位序列
private string GetShiftSequence(string s) {
if (s.Length <= 1) {
return ""; // 长度为1的字符串没有移位序列
}
var shifts = new List<int>();
// 计算相邻字符之间的差值
for (int i = 1; i < s.Length; i++) {
// 计算差值并确保是正数(处理循环移位)
int shift = (s[i] - s[i-1] + 26) % 26;
shifts.Add(shift);
}
// 将差值序列转换为字符串作为键
return string.Join(",", shifts);
}
}
Python实现
class Solution:
def groupStrings(self, strings: List[str]) -> List[List[str]]:
groups = {}
for s in strings:
# 计算标准形式(移位序列)
key = self._get_shift_sequence(s)
# 将当前字符串加入相应的组
if key not in groups:
groups[key] = []
groups[key].append(s)
# 返回所有分组
return list(groups.values())
def _get_shift_sequence(self, s: str) -> str:
if len(s) <= 1:
return "" # 长度为1的字符串没有移位序列
shifts = []
# 计算相邻字符之间的差值
for i in range(1, len(s)):
# 计算差值并确保是正数(处理循环移位)
shift = (ord(s[i]) - ord(s[i-1])) % 26
shifts.append(shift)
# 将差值序列转换为字符串作为键
return ",".join(map(str, shifts))
C++实现
class Solution {
public:
vector<vector<string>> groupStrings(vector<string>& strings) {
unordered_map<string, vector<string>> groups;
for (const string& s : strings) {
// 计算标准形式(移位序列)
string key = getShiftSequence(s);
// 将当前字符串加入相应的组
groups[key].push_back(s);
}
// 返回所有分组
vector<vector<string>> result;
for (const auto& pair : groups) {
result.push_back(pair.second);
}
return result;
}
private:
// 计算字符串的移位序列
string getShiftSequence(const string& s) {
if (s.length() <= 1) {
return ""; // 长度为1的字符串没有移位序列
}
string key;
// 计算相邻字符之间的差值
for (int i = 1; i < s.length(); i++) {
// 计算差值并确保是正数(处理循环移位)
int shift = (s[i] - s[i-1] + 26) % 26;
// 将差值转换为字符以减少键的长度
key += 'a' + shift;
// 使用分隔符避免歧义,例如 "1,10" 和 "11,0"
if (i < s.length() - 1) {
key += ',';
}
}
return key;
}
};
性能分析
时间复杂度
- 遍历字符串数组:O(n),其中 n 是字符串数组的长度
- 对于每个字符串计算移位序列:O(m),其中 m 是字符串的平均长度
- 总体时间复杂度:O(n * m)
空间复杂度
- 存储分组的哈希表:O(n),因为每个字符串都会被存储一次
- 存储移位序列的额外空间:O(m),用于临时存储每个字符串的移位序列
- 总体空间复杂度:O(n * m)
代码亮点
- 移位序列计算:通过计算相邻字符的差值序列,巧妙地识别了移位等价的字符串
- 哈希表分组:利用哈希表高效地对具有相同移位序列的字符串进行分组
- 模运算处理循环:使用模26运算来处理字母的循环移动
- 边界情况处理:单独处理了长度为1的字符串
优化方向
- 移位序列表示优化:可以将差值直接表示为字符,减少哈希表键的长度
- 分隔符选择:确保使用合适的分隔符避免移位序列的歧义
- 初始化优化:可以根据问题规模预先为哈希表和结果数组分配适当的空间