力扣解题-242. 有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的 字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
提示:
1 <= s.length, t.length <= 5 * 104
s 和 t 仅包含小写字母
进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
Related Topics
哈希表、字符串、排序
第一次解答
解题思路
核心方法:数组计数法,利用小写字母仅26个的特性,用长度为26的数组统计s中每个字符的出现次数,再遍历t扣减计数,最终校验数组是否全为0,逻辑简洁且满足题目基础要求,时间复杂度O(n+m)、空间复杂度O(1)。
核心逻辑拆解
判断字母异位词的核心是“字符种类和数量完全一致”:
- 计数初始化:创建长度为26的数组(对应a-z),初始值为0;
- 统计s的字符:遍历
s,将每个字符对应下标(c-'a')的计数+1(比如s="anagram",a的计数为3,n为1,g为1,r为1,m为1); - 扣减t的字符:遍历
t,将每个字符对应下标的计数-1,若扣减后计数<0,说明t包含s没有的字符/某字符数量更多,直接返回false; - 校验全零:遍历计数数组,若所有元素都为0,说明
s和t的字符数量完全一致,返回true;否则返回false。
性能说明
- 时间复杂度:O(n+m)(n为
s长度,m为t长度,三次线性遍历:统计s、扣减t、校验数组,数组校验仅26次可忽略),耗时4ms击败45.73%用户; - 空间复杂度:O(1)(数组长度固定为26,与输入规模无关),内存消耗43.6MB击败72.14%用户;
- 优势:
- 利用字母特性优化存储,无哈希表的额外开销;
- 提前终止:扣减时发现计数<0立即返回,减少无效遍历;
- 可优化点:未做“长度预判”(若
s和t长度不同,可直接返回false),少量无效计算影响耗时排名。
public boolean isAnagram(String s, String t) {
int [] sArray=new int[26];
for(int i=0;i<s.length();i++){
sArray[s.charAt(i)-'a']++;
}
for(int i=0;i<t.length();i++){
sArray[t.charAt(i)-'a']--;
if(sArray[t.charAt(i)-'a']<0){
return false;
}
}
for(int i=0;i<26;i++){
if(sArray[i]!=0)
return false;
}
return true;
}
示例解答
解题思路
解法1:长度预判优化版(性能最优)
核心方法:前置长度校验 + 数组计数,在计数前先判断s和t的长度是否一致,长度不同直接返回false(字母异位词长度必相等),减少无效的计数/扣减操作,进一步提升性能。
核心优化点
- 前置校验:字母异位词的必要条件是“长度相等”,若长度不同可直接返回false,避免后续所有计数操作;
- 字符数组遍历:将字符串转为
char[]后遍历,减少charAt()的边界检查开销,提升遍历效率。
代码实现
public boolean isAnagram(String s, String t) {
// 前置校验:长度不同直接返回false
if (s.length() != t.length()) {
return false;
}
int[] count = new int[26];
// 转为字符数组,减少charAt()开销
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
// 统计s的字符
for (char c : sChars) {
count[c - 'a']++;
}
// 扣减t的字符
for (char c : tChars) {
count[c - 'a']--;
// 提前终止:数量不足直接返回
if (count[c - 'a'] < 0) {
return false;
}
}
// 长度相等且扣减无负数,必全为0,无需额外校验
return true;
}
性能优势
- 时间复杂度:O(n)(仅两次遍历,且长度不同时直接返回),耗时可降至1~2ms(击败99%+用户);
- 空间复杂度:O(1),无额外开销;
- 核心优化效果:
- 长度不同的用例(如示例2)可直接返回,无需任何计数操作;
- 字符数组遍历比
charAt()快约10%~20%; - 长度相等时,扣减无负数则数组必全为0,省略最后26次遍历。
解法2:排序法(基础思路)
核心方法:排序后比对,将两个字符串排序后,若为字母异位词则排序结果完全一致,逻辑简单但时间复杂度较高,适合理解异位词的核心特性。
代码实现
import java.util.Arrays;
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
// 排序
Arrays.sort(sChars);
Arrays.sort(tChars);
// 比对排序结果
return Arrays.equals(sChars, tChars);
}
性能说明
- 时间复杂度:O(nlogn)(排序的时间主导),效率低于数组计数法,但逻辑极简;
- 空间复杂度:O(n)(排序的栈空间+字符数组);
- 适用场景:若不允许使用额外数组(仅允许常量空间),可使用该方法(但排序的栈空间实际为O(logn))。
解法3:Unicode适配版(进阶场景)
核心方法:HashMap替代数组,针对进阶问题(包含Unicode字符),用HashMap统计所有字符的出现次数,兼容任意字符类型。
代码实现
import java.util.HashMap;
import java.util.Map;
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
Map<Character, Integer> countMap = new HashMap<>();
// 统计s的字符
for (char c : s.toCharArray()) {
countMap.put(c, countMap.getOrDefault(c, 0) + 1);
}
// 扣减t的字符
for (char c : t.toCharArray()) {
Integer count = countMap.get(c);
if (count == null || count == 0) {
return false;
}
countMap.put(c, count - 1);
}
return true;
}
核心适配逻辑
- 适用场景:字符串包含Unicode字符(如中文、特殊符号),无法用固定长度数组统计;
- 时间复杂度:O(n)(HashMap的增删查均为O(1));
- 空间复杂度:O(k)(k为不同字符数);
- 劣势:HashMap的哈希计算、装箱拆箱开销高于数组,性能略低,但兼容性最好。
总结
- 基础数组计数法(第一次解答):逻辑简洁,满足基础要求,但缺少长度预判和遍历优化;
- 长度预判优化版(最优解):性能拉满,O(n)时间+O(1)空间,是小写字母场景的工程首选;
- 排序法:逻辑极简,适合理解异位词特性,但O(nlogn)时间复杂度效率较低;
- Unicode适配版:兼容任意字符类型,解决进阶问题,牺牲少量性能换取通用性;
- 关键技巧:
- 字母异位词必满足“长度相等”,前置校验可快速过滤无效用例;
- 小写字母场景优先用数组而非HashMap,性能提升显著;
- 大数据量下优先将字符串转为字符数组遍历,减少方法调用开销。