力扣解题-242. 有效的字母异位词

5 阅读6分钟

力扣解题-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)。

核心逻辑拆解

判断字母异位词的核心是“字符种类和数量完全一致”:

  1. 计数初始化:创建长度为26的数组(对应a-z),初始值为0;
  2. 统计s的字符:遍历s,将每个字符对应下标(c-'a')的计数+1(比如s="anagram",a的计数为3,n为1,g为1,r为1,m为1);
  3. 扣减t的字符:遍历t,将每个字符对应下标的计数-1,若扣减后计数<0,说明t包含s没有的字符/某字符数量更多,直接返回false;
  4. 校验全零:遍历计数数组,若所有元素都为0,说明st的字符数量完全一致,返回true;否则返回false。
性能说明
  • 时间复杂度:O(n+m)(n为s长度,m为t长度,三次线性遍历:统计s、扣减t、校验数组,数组校验仅26次可忽略),耗时4ms击败45.73%用户;
  • 空间复杂度:O(1)(数组长度固定为26,与输入规模无关),内存消耗43.6MB击败72.14%用户;
  • 优势:
    1. 利用字母特性优化存储,无哈希表的额外开销;
    2. 提前终止:扣减时发现计数<0立即返回,减少无效遍历;
  • 可优化点:未做“长度预判”(若st长度不同,可直接返回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:长度预判优化版(性能最优)

核心方法:前置长度校验 + 数组计数,在计数前先判断st的长度是否一致,长度不同直接返回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),无额外开销;
  • 核心优化效果:
    1. 长度不同的用例(如示例2)可直接返回,无需任何计数操作;
    2. 字符数组遍历比charAt()快约10%~20%;
    3. 长度相等时,扣减无负数则数组必全为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的哈希计算、装箱拆箱开销高于数组,性能略低,但兼容性最好。

总结

  1. 基础数组计数法(第一次解答):逻辑简洁,满足基础要求,但缺少长度预判和遍历优化;
  2. 长度预判优化版(最优解):性能拉满,O(n)时间+O(1)空间,是小写字母场景的工程首选;
  3. 排序法:逻辑极简,适合理解异位词特性,但O(nlogn)时间复杂度效率较低;
  4. Unicode适配版:兼容任意字符类型,解决进阶问题,牺牲少量性能换取通用性;
  5. 关键技巧:
    • 字母异位词必满足“长度相等”,前置校验可快速过滤无效用例;
    • 小写字母场景优先用数组而非HashMap,性能提升显著;
    • 大数据量下优先将字符串转为字符数组遍历,减少方法调用开销。