力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解

👋 欢迎阅读

🎯 欢迎来到「判定字符是否唯一」题解之旅! 本文将带你从"检查一串字符里有没有重复的字母"这一直观场景出发,深入理解位图(位运算) 的巧妙运用,并掌握如何用一个整数的二进制位记录字符出现情况在 O(n) 时间内判断唯一性

在开始之前,建议你先:

  • 了解题目背景 :这是 LCR 191(面试题 01.01)题,给定字符串 astr,判断其中所有字符是否全部不同 ------有重复返回 false,全部唯一返回 true。本质上,问题转化为对每个字符做"是否出现过"的查询与标记 ,而小写字母只有 26 个,正好可以用一个 int 的 26 个比特位表示。

  • 明确学习目标 :掌握位图标记与查询 技术,理解 (b >> ch) & 1 判存在b |= (1 << ch) 置位 的原理,并熟练处理空串字符集范围等边界情况。

  • 准备好环境 :建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如 astr = "leetcode" 输出 falseastr = "abc" 输出 true)。

本文将从问题转化、位图原理、查重与置位、边界防护 到代码实现,层层递进。即使你对位运算 还不熟悉,我们也会从"给 26 个字母每人一个座位,来了就看看座位有没有人"这一直觉出发,让你轻松抓住核心思想 ------一位一字符,查空即坐,重坐即重。现在,让我们一起用位图点名,判断字符是否唯一吧! 🔍🎯

🔥愿旖旎 · 个人主页

📘学习专栏: 《算法专栏》《LangChain学习》《贪心算法》

🌄 钱塘江上潮信来,今日方知我是我

当前学习内容**:** 《位运算》

一.题目

面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode)

二、算法分析

一、问题分析(前置分析)

  • 题目要求:判断字符串 astr所有字符是否唯一 (无重复),有重复返回 false,否则 true
  • 关键约束:字符限定为小写字母 (26 个);空串 视为唯一;不允许使用额外数据结构(面试原题常附加此条件)。
  • 核心思路:暴力做法用双重循环 两两比较,总代价 O(n²);用哈希思想 只需知道"每个字符是否出现过",小写字母仅 26 种,恰好可以用一个 int 的 26 个比特位 当"极简哈希表",查询与标记都是 O(1),总复杂度 O(n)

📌 例子:为什么可以用一个整数代替哈希表

astr = "abca":'a'、'b'、'c' 依次出现,第 4 个字符又是 'a'------只需记住"a 来过",就能判定重复。26 个小写字母的"来过与否"是 26 个布尔值 ,正好对应 int 的 26 个比特位(int 有 32 位,绰绰有余),一个整数 4 字节即可完整表示 ,比 bool[26](26 字节)更省空间。

二、算法策略(位图标记 + 查重)

核心步骤:

  1. 初始化b = 0(32 个比特全 0,表示"谁都没来过")。
  2. 遍历每个字符 :取字符相对 'a' 的偏移 ch = astr[i] - 'a'(范围 0~25)。
  3. 查询是否出现过if ((b >> ch) & 1) return false;(把第 ch 位移到最低位,与 1 相与------结果为 1 说明该位已置位,字符来过)。
  4. 标记出现b |= (1 << ch);(把第 ch 位置 1,表示"该字符来过了")。
  5. 返回 :遍历结束未发现重复,返回 true

📊 示例astr = "abca"):

步骤 字符 ch b 的二进制(低位在右) 查询 (b>>ch)&1 操作 结果
初始 --- --- ...0000 --- --- ---
i=0 'a' 0 ...0000 0(未出现) 置位...0001 继续
i=1 'b' 1 ...0001 0(未出现) 置位...0011 继续
i=2 'c' 2 ...0011 0(未出现) 置位...0111 继续
i=3 'a' 0 ...0111 1(已出现) 返回 false false

i=3 时 'a' 对应的第 0 位已是 1,判定重复,返回 false ✅;若换成 "abc",三次查询全为 0,遍历结束返回 true

三、正确性说明(简单版本)

  • 位图语义精确b 的第 ch 位为 1 ⇔ 字符 'a'+ch 已经出现过 ;为 0 ⇔ 尚未出现。每个小写字母占据唯一且固定 的比特位,不会互相干扰(26 < 32,无位溢出)。
  • 查询公式正确(b >> ch) & 1 把第 ch 位移到最低位后与 1 相与,恰好取出该位的值 ,为 1 即重复------与"该字符是否出现过"逐字对应,不会误判
  • 置位公式正确b |= (1 << ch)或运算 把第 ch 位强制置 1,且不影响其他位(或 1 置位、或 0 保持),标记精确。
  • 覆盖全部字符 :遍历每个字符都做一次"查询 + 置位",任何重复都会在第二次出现时被立即捕获 ,不漏判;全不重复则返回 true

📌 例子:为什么 (b >> ch) & 1 能取到第 ch 位

b 的二进制为 ...0111('a'、'b'、'c' 已出现),检查 'b'(ch=1):右移 1 位得 ...0011,与 1 相与 → 1,说明 'b' 出现过 ;检查 'd'(ch=3):右移 3 位得 ...0000,与 1 相与 → 0,说明 'd' 没出现过 ------右移把目标位挪到最低位,与 1 相与即读出该位,这就是"取第 ch 位"的标准写法。

四、实现细节(边界防护)

  • 初始化:b = 0所有位为 0 ,表示空集)、n = astr.size()
  • 边界防护:空串n = 0,循环不进入,直接返回 true(空串无重复字符,符合题意);ch = astr[i] - 'a' 范围 0~25,1 << ch 最大 1 << 25远小于 int 上限 ,无溢出;字符集限小写字母 ,若超出则 ch 可能 ≥ 32 导致位移溢出(UB),需提前扩展位图。
  • 复杂度:时间 O(n) (每个字符一次查询 + 一次置位),空间 O(1)(只有一个 int,4 字节)。
  • 关键操作if ((b >> ch) & 1) return false;(查重)、b |= (1 << ch);(置位标记)。

📌 例子:空串与单字符的边界

astr = ""n = 0循环一次都不执行 ,直接返回 true------空串没有任何字符,唯一性成立。astr = "a":ch=0,(0 >> 0) & 1 = 0(未出现),置位后 b = 1,遍历结束返回 true------单字符天然唯一,两个边界都由同一套逻辑自然处理。

五、返回值(目标映射)

  • 返回 false发现重复字符 (第二次出现时立即返回);返回 true遍历完毕无重复,对应题目"字符是否唯一"的判定。

三.代码

cpp 复制代码
class Solution
{
public:
    bool isUnique(string astr)
    {
        int b = 0;                  // 位图:第 ch 位为 1 表示字符 'a'+ch 已出现过
        int n = astr.size();

        // 1. 遍历每个字符:查重 -> 置位
        for (int i = 0; i < n; i++)
        {
            // 获取字符偏移:'a' 对应第 0 位,'z' 对应第 25 位
            int ch = astr[i] - 'a';

            // 2. 查重:把第 ch 位移到最低位后与 1 相与,为 1 说明该字符已出现
            if ((b >> ch) & 1)
            {
                return false;       // 出现重复字符,立即返回
            }

            // 3. 置位标记:把第 ch 位置 1,表示该字符已经来过(不影响其他位)
            b |= (1 << ch);
        }
        return true;                // 4. 遍历结束无重复,字符全部唯一
    }
};

四、易错点分析

难点1:(b >> ch) & 1 的取位逻辑

cpp 复制代码
if ((b >> ch) & 1)    // 取出第 ch 位

b >> ch 把第 ch 位移到最低位& 1 只保留最低位------两步合起来就是"读出第 ch 位 "。若写成 b & (1 << ch) 也能判断(结果为 0 或非 0),但不能写成 b & ch (那是把 ch 当掩码,语义完全错误)。理解"右移到最低位再与 1"这个标准范式,可以避免大量位运算错误。

难点2:b |= (1 << ch) 而非 b += (1 << ch)

cpp 复制代码
b |= (1 << ch);    // 正确:或运算置位
// b += (1 << ch); // 危险:若该位已是 1,加法会产生进位污染其他位

置位必须用或运算1 << ch 只有第 ch 位是 1,或其他位都能正确置位;若用 +=,当该位已是 1 时相加会进位 ,把第 ch+1 位也变成 1,污染其他字符的标记 ,导致后续误判。虽然本代码在置位前已排除"该位为 1"的情况(重复会先返回),但养成用 |= 的习惯才是安全的。

难点3:字符集范围与位移溢出

cpp 复制代码
int ch = astr[i] - 'a';    // 小写字母:0 ~ 25
b |= (1 << ch);            // 1 << 25 安全;若 ch >= 32 则 UB

本题限定小写字母 (26 个),ch ∈ [0,25]1 << ch 最大 2^25,在 int 范围内绝对安全 。若字符集扩展到大小写 + 数字 (62 个)或 ASCII (128 个),ch 可能 ≥ 32,1 << ch位移溢出 (UB)------此时必须改用 long long(64 位)或多个 int 组成位图,或退回哈希集合。

难点4:为什么位图比 bool[26] 更优

cpp 复制代码
int b = 0;                // 4 字节表示 26 个状态
bool vis[26] = {false};   // 26 字节表示同样 26 个状态

bool[26] 虽然简单,但每个元素占 1 字节 (共 26 字节);位图把 26 个状态压缩进 32 位 (4 字节),空间缩小到 1/8 以内 ,且查询/置位都是单条位运算指令 ,比数组寻址更快。这是"用时间换空间?不,是同时省空间又提速 "的典型------状态数少且固定用位图,状态多或用哈希

难点5:提前判断"字符串长度 > 26 必重复"的优化

cpp 复制代码
if (astr.size() > 26) return false;   // 可选优化:26 个字母装不下更多唯一字符

小写字母只有 26 个,若字符串长度 超过 26 ,根据鸽巢原理 必然有重复,可直接返回 false无需遍历

五、流程图

🎯 闭幕

🎉 恭喜你完成了「面试题 01.01. 判定字符是否唯一」问题的学习!

为了巩固知识并进一步拓展,建议你:

🚀 动手实践
在 LeetCode 上提交代码,尝试不同的测试用例。

💡 深入思考

  • 本题使用一个 int 位图 来标记字符是否出现过,'a' 对应第 0 位,'z' 对应第 25 位。为什么一个 int 就足够?

  • 查重时使用 (b >> ch) & 1,置位时使用 b |= (1 << ch)这两种位运算分别实现了什么功能? 如果换成 b & (1 << ch) 来判断是否出现过,是否等价?

  • 题目特别提示"如果不使用额外的数据结构,会很加分"。位图算不算额外的数据结构? 它的空间复杂度是多少?为什么说它比哈希表更节省空间?

  • 如果字符串长度 超过 26 且只包含小写字母,是否一定存在重复字符?能否在遍历前直接根据长度返回结果? 这样做有什么好处?

如果你觉得本文对你有所帮助,欢迎:


📌 深入思考答案

  • 一个 int 足够,因为小写字母只有 26 个,int 通常有 32 位,足以容纳 26 个标记位。

  • (b >> ch) & 1取出第 ch 位 并判断是否为 1,b |= (1 << ch)将第 ch 位置 1 。换成 b & (1 << ch) 也能判断是否出现过,结果等价,但前者更直观地展示了"取位"动作。

  • 位图不算传统意义上的额外数据结构,它只是一个整型变量,空间 O(1)。相比哈希表 O(字符种类数),位图更节省空间,且位运算速度极快。

  • 若字符串长度 超过 26 且只含小写字母,根据鸽巢原理 必然有重复 ,可在开头加 if (astr.size() > 26) return false;,提前剪枝,避免无谓遍历。

👍 点赞 / 收藏
👤 关注作者 ,获取更多题解
💬 留言交流你的疑问或优化思路

祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨

相关推荐
wzdark4 小时前
大规模并行计算中的负载均衡算法研究4
算法
奇思妙想聪明勤奋的小羊4 小时前
DeepAgents第5章:子Agent 与上下文隔离—让 Agent学会委派
人工智能·python·学习·语言模型
吞下星星的少年·-·4 小时前
C++ 萌新语法入门篇
c++·算法比赛
Because_of_Her14 小时前
并查集-听课笔记
笔记·算法·并查集
霍霍的袁4 小时前
【C++】map 和 set 的使用 | 从用法到底层
开发语言·c++·学习·visual studio
码流子4 小时前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
彧azz4 小时前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
another heaven5 小时前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark5 小时前
从算法设计模式看编程思维的抽象能力4
算法