转换成小写字母 图解题解
这道题到底在问什么
- 输入
- s = "Hello"
- 输出
- "hello" (只有 H 是大写,转成 h)
- 输入
- s = "LOVELY"
- 输出
- "lovely" (六个字母全大写,全部转小写)
- 输入
- s = "here"
- 输出
- "here" (本就全小写,原样返回)
先想最直接的笨办法
先把整条串摆出来。大写字母 A、C、E、G、I 夹在小写 b、d、f、h、j 中间。我们从第 0 个开始,一个一个过。(动画第 4 步)
最优解:为什么这么做
一句话答案:LeetCode 709 转换成小写字母:逐字符扫,码落在 65 到 90 的大写字母按位或 32(差的正是第 5 位)变小写,其余原样,一趟搞定,时间 O(n)、空间 O(n)。
哪些字符要改,其它的原样吗
给一个字符串 s,把里面的大写字母 A 到 Z 换成对应的小写,其余字符——小写、数字、空格、符号——全部原样保留,返回新串。题面例子 s="Hello" 只有 H 是大写,转成 "hello";s="LOVELY" 六个字母全大写,整串变 "lovely";s="here" 本就全小写,原样返回。范围很干净:认准 A 到 Z 这 26 个,别的都不碰。
对每个字符都 +32,为什么串会乱套
小写字母的 ASCII 码,也就是它在计算机里对应的编号,比同一个大写正好大 32,于是很容易想着对整串逐字符 +32 一了百了。可这样非大写字符全遭殃:空格的码是 32,+32 变成 64 也就是 @;数字 0 的码 48,+32 变成 80 也就是 P;已经是小写的字母再 +32 更会直接冲出字母区。所以 +32 这一步不能无脑做,必须先问一句『它是不是大写』。
大小写在二进制里只差哪一位
把码摊成二进制就看明白了。大写 A 到 Z 是 65 到 90,小写 a 到 z 是 97 到 122,同一个字母的大小写只差第 5 位(值 32)那一个比特:大写这位是 0,小写这位是 1。所以要把大写变小写,只需把第 5 位从 0 置成 1,也就是按位或 32(把二进制第 5 位强行拨到 1),写作 c | 32。对大写字母来说,c | 32 和 c + 32 结果完全一样,因为那一位原本是 0,置 1 和加 32 是同一件事。
判范围、拨那一位、拼回去
落到代码就三步走一趟:从头到尾遍历每个字符 c,先看它的码是不是落在 65 到 90 之间(Python 里直接用 c.isupper() 问,C++、Java 用范围判);是大写就 c | 32 拨出小写,不是就原样留下;把每一步的结果依次拼成新串返回。整条串只扫一遍,看过就不再回头。
Hello 五个字符,逐个判该不该动
拿题面的 "Hello" 走一遍。第 0 个 H,码是 72,落在 65 到 90 里,是大写,该转:72 | 32 得 104,也就是 h(72 + 32 同样是 104)。第 1 个 e,码 101,超过 90 不是大写,原样留 e。第 2 个 l、第 3 个 l,码都是 108,不在范围,原样。第 4 个 o,码 111,同样原样。五个字符依次拼起来就是 "hello",全程一趟扫描。
边界卡 65 到 90,差一位就漏 Z 或错收方括号
范围要卡得死死的,严格 65 到 90。要是把上界写成 91,方括号 [(码 91)会被误当字母 +32 改成花括号;把下界写成 64,@(码 64)也会跟着遭殃。字符表上这几个邻居就贴在字母两端,数错一位当场出错。还有个小误会:以为 | 32 和 + 32 是两码事——对大写字母它俩完全等价,放心用哪个都行。
复杂度上,每个字符只看一次,时间 O(n),n 是串长;结果得装进一条等长的新串,空间 O(n)。三种边界心里先跑一遍最稳:"LOVELY" 全大写、整串都要转,"here" 全小写、一个都不动,还有只剩一个字符的串,照样是判范围再决定。
▶ 动画逐步走查(共 23 步)——想跟着动画一帧帧对照就展开
- 3记住这把尺子:大写小写在 ASCII 表里成对排列,每对正好差 32。下面每一帧都在套「在范围就 +32,不在就放过」。
- 4先把整条串摆出来。大写字母 A、C、E、G、I 夹在小写 b、d、f、h、j 中间。我们从第 0 个开始,一个一个过。
- 5轮到第 0 个字符 「A」,它的 ASCII 码是 65。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 6「A」 是大写,该转。给它的码 65 加上 32(用位运算 | 32 一步到位)得到 97,也就是小写 「a」。第 0 格变绿,表示这一格已经转好了。
- 7轮到第 1 个字符 「b」,它的 ASCII 码是 98。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 8「b」 的码 98 不在 65 到 90 这一段,它本来就是小写,什么都不用做,原样留下。第 1 格标蓝,表示看过、未改。
- 9轮到第 2 个字符 「C」,它的 ASCII 码是 67。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 10「C」 是大写,该转。给它的码 67 加上 32(用位运算 | 32 一步到位)得到 99,也就是小写 「c」。第 2 格变绿,表示这一格已经转好了。
- 11轮到第 3 个字符 「d」,它的 ASCII 码是 100。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 12「d」 的码 100 不在 65 到 90 这一段,它本来就是小写,什么都不用做,原样留下。第 3 格标蓝,表示看过、未改。
- 13轮到第 4 个字符 「E」,它的 ASCII 码是 69。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 14「E」 是大写,该转。给它的码 69 加上 32(用位运算 | 32 一步到位)得到 101,也就是小写 「e」。第 4 格变绿,表示这一格已经转好了。
- 15轮到第 5 个字符 「f」,它的 ASCII 码是 102。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 16「f」 的码 102 不在 65 到 90 这一段,它本来就是小写,什么都不用做,原样留下。第 5 格标蓝,表示看过、未改。
- 17轮到第 6 个字符 「G」,它的 ASCII 码是 71。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 18「G」 是大写,该转。给它的码 71 加上 32(用位运算 | 32 一步到位)得到 103,也就是小写 「g」。第 6 格变绿,表示这一格已经转好了。
- 19轮到第 7 个字符 「h」,它的 ASCII 码是 104。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 20「h」 的码 104 不在 65 到 90 这一段,它本来就是小写,什么都不用做,原样留下。第 7 格标蓝,表示看过、未改。
- 21轮到第 8 个字符 「I」,它的 ASCII 码是 73。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 22「I」 是大写,该转。给它的码 73 加上 32(用位运算 | 32 一步到位)得到 105,也就是小写 「i」。第 8 格变绿,表示这一格已经转好了。
- 23轮到第 9 个字符 「j」,它的 ASCII 码是 106。先问:这个码落在 65 到 90 之间吗,也就是它是不是大写字母?
- 24「j」 的码 106 不在 65 到 90 这一段,它本来就是小写,什么都不用做,原样留下。第 9 格标蓝,表示看过、未改。
- 25十个字符全部过了一遍。绿色的 5 个是从大写转来的,蓝色的 5 个本就是小写、原样保留,拼起来就是答案 abcdefghij。整个过程一遍扫描、没有回头。
⚠️ 容易写错的地方
✗ 错:用 +32 时忘了先判是不是大写
✓ 对:只对 A 到 Z 加 32,其它一律不动
给小写或数字、符号也加 32 会把它们改成别的字符,结果全乱
✗ 错:把判定范围写成 65 到 91 或 64 到 90
✓ 对:严格 65 ≤ c ≤ 90
边界差一位会漏掉 Z 或误把 [ 当字母,字符表上要数准
✗ 错:以为 | 32 和 + 32 不一样
✓ 对:对大写字母两者结果相同
大写字母的 ASCII 第 5 位(值 32)本来是 0,加 32 和按位或 32 等价
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def toLowerCase(self, s: str) -> str:
return "".join([chr(ord(c) | 32) if c.isupper() else c for c in s])C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
string toLowerCase(string s) {
for (char& c : s) {
if (c >= 'A' && c <= 'Z') {
c |= 32;
}
}
return s;
}
};Java
import java.util.*;
class Solution {
public String toLowerCase(String s) {
char[] cs = s.toCharArray();
for (int i = 0; i < cs.length; ++i) {
if (cs[i] >= 'A' && cs[i] <= 'Z') {
cs[i] |= 32;
}
}
return String.valueOf(cs);
}
}复杂度
时间
O(n)
每个字符只看一次,n 是串长
空间
O(n)
需要一个等长的结果串(字符串通常不可原地改);C++ 传值副本上原地改也是 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 转换成小写字母 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么大写转小写可以用位运算 | 32?+
看 ASCII 二进制就清楚:大写 A 到 Z 是 65 到 90,小写 a 到 z 是 97 到 122,同一个字母的大小写只差第 5 位(值 32)那一个比特,大写该位是 0、小写是 1。所以把大写的第 5 位置成 1(也就是 | 32)就变小写;反过来把该位清成 0(& ~32)就变大写。比起加加减减,这样更贴近『大小写只差一位』的本质,也不用担心记错方向。
为什么一定要先判范围,不能直接给每个字符都 +32?+
因为 +32 只对大写字母有意义,对别的字符是破坏。空格码 32,+32 变 64 成了 @;数字 0 码 48,+32 变 80 成了 P;已经是小写的字母再 +32 会直接跑出字母区变成别的符号。题目只要求动 A 到 Z,其余原样,所以必须先判『码在不在 65 到 90 之间』,是大写才 +32,否则一律放过。
如果不让用库函数 tolower,怎么写最稳?+
就用本题这套:遍历每个字符,判它的码是否在 65 到 90 之间,是就 c | 32 或 c + 32 变小写,否则原样保留,最后拼成新串。关键是一定先判范围再转,不能无脑加 32,否则会改坏非大写字符。这其实也是 tolower 内部大致在做的事,自己写一遍反而更清楚它凭什么成立。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 转换成小写字母 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。