题目描述

✅ 面试题 01.01. 判定字符是否唯一

image-20260928230516915

题意分析

判断字符串中的字符是否互不重复。题目限定只含 26 个小写字母,并提出不用额外数据结构的进阶要求;因此可把“哪些字母已出现”压缩到一个整数中,空串也满足字符唯一。

解法:整数位掩码记录已出现字母

核心思路

[!blue]

判断重复只需知道一个字母是否出现过,不必记录完整出现次数。给 a 到 z 分配第 0 到第 25 位,令 mask 中的置位表示已扫描前缀出现过对应字母。开始时前缀为空,所以 mask = 0。

对当前字符,先用 字符 - 'a' 得到偏移,再计算 bit = 1 << 偏移。bit 只有这个字母对应的一位为一,因此 mask & bit 非零,恰好说明此前已经出现过同一个字母;立即返回 false 即可。

如果没有交集,用 mask |= bit 把这一位设为一。按位或会保留所有旧标记,只登记当前字母,因此更新后 mask 仍准确表示整个已扫描前缀。扫描到末尾都没触发重复,就说明所有字符互不相同,返回 true。

26 个标记位可以放进一个整数,满足不建立集合或数组的进阶要求。这个做法依赖题目给定的字符范围;空串不会进入循环,自然返回 true。

解题步骤

  1. 令 mask = 0,表示尚未见过任何字母。
  2. 为当前字母计算只有一位为一的 bit。
  3. 若 mask & bit 非零,说明该字母重复,返回 false。
  4. 否则把 bit 按位或进 mask,继续扫描;全部通过后返回 true。

代码实现

class Solution {
    public boolean isUnique(String astr) {
        // 每个置位表示已扫描前缀中出现过对应字母。
        int mask = 0;

        for (int i = 0; i < astr.length(); i++) {
            int bit = 1 << (astr.charAt(i) - 'a');

            // 先检测旧状态,确认未重复后才把当前字母登记进去。
            if ((mask & bit) != 0) {
                return false;
            }

            mask |= bit;
        }

        return true;
    }
}
func isUnique(astr string) bool {
    // 每个置位表示已扫描前缀中出现过对应字母。
    mask := 0
    for i := 0; i < len(astr); i++ {
        bit := 1 << (astr[i] - 'a')
        // 先检测旧状态,确认未重复后才把当前字母登记进去。
        if mask&bit != 0 {
            return false
        }
        mask |= bit
    }
    return true
}

复杂度分析

  • 时间复杂度:$O(n)$ 上界,其中 n 为字符串长度,每个字符只做常数次位运算,发现重复即可提前返回。
  • 空间复杂度:$O(1)$,只使用整数掩码与当前位标记。

关键点总结

[!green]

  • 固定的 26 字母字符集,让集合成员关系可以用整数位表示。
  • 按位与检查旧标记,按位或登记新标记。
  • 每轮结束时,掩码恰好对应已经处理过的前缀。

易错点总结

[!yellow]

  • 必须先检查再登记,否则当前字符第一次出现也会被误判为重复。
  • 不能只对所有字母做异或后检查最终值;重复字母可能相互抵消,最终状态无法说明中途是否重复。
  • 位偏移是 字符 - 'a',不能直接用字符编码当偏移量。
  • 这段代码只处理题目限定的小写字母,不能把同一套 26 位映射直接用于更大的字符集。

相似题目

题目 难度 关联与区别
217. 存在重复元素 简单 同样判断重复是否存在,本题字符集只有26个小写字母,可用位掩码替代通用哈希集合。
318. 最大单词长度乘积 中等 同样用一个位表示一种字母,本题检查是否重复置位,原题用掩码交集判断两个词是否共用字母。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2023/86203267
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!