密码翻译 的题解
记住只在没有思路时使用题解,不要从它复制粘贴代码。请尊重题目和题解的作者。
在解题之前提交题解的代码会导致封禁。
在解题之前提交题解的代码会导致封禁。
作者:
概述
本题要求把一行按“每个字母替换为后继字母”加密得到的密文解密还原,即把每个字母替换为它在字母表中的前驱字母。核心解法是利用加密与解密互为逆运算这一性质,对每个字符做一次常数时间的映射,字母以外的所有字符原样输出。
分析
核心观察
加密规则把 个小写字母与
个大写字母各自映射到同类字母内部,是一个一一对应的置换,因此解密就是它的逆置换:密文字母往前移动一位即得明文,往前越过
a 或 A 时回绕到 z 或 Z。非字母字符在加密时本就不变,解密时同样原样保留。
思路
记密文的第 个字符为
,解密后得到的第
个字符为
。加密做的事是“后继”,即
a 变成 b、b 变成 c、……、y 变成 z,而 z 回绕成 a,大写字母同理。解密要把它倒过来,于是规则是“前驱”:
b变成a、c变成b、……、z变成y,特例a回绕成z;B变成A、C变成B、……、Z变成Y,特例A回绕成Z;- 其余字符保持不变。
用公式概括就是
回绕的两个特例单独处理:a 的前驱是 z,A 的前驱是 Z。
这里有两个容易出错的地方。第一,不能对所有字符统一做 ASCII 码减一:那样空格、标点等非字母字符会被改坏,而且 a 会变成反引号而不是 z,回绕必须显式处理。第二,回绕要分大小写,a 对应 z、A 对应 Z,把 a 变成 Z 或者把 A 变成 z 都是错的。
此外,题面背景中有一条更新说明:曾经有数据把 @ 错误地当成了 A,该问题已被修复。@ 不是字母,按规则原样输出,不能参与任何字母变换,这一点在解密实现里必须遵守。
具体示例
以样例 Ifmmp ! Ipx bsf zpv! 为例,逐段处理如下。
| 密文片段 | 处理 | 明文片段 |
|---|---|---|
Ifmmp |
I 是大写字母,前移为 H;f、m、m、p 依次前移为 e、l、l、o |
Hello |
! |
非字母,原样保留 | ! |
| 连续两个空格 | 非字母,原样保留 | 连续两个空格 |
Ipx |
I、p、x 前移为 H、o、w |
How |
bsf |
b、s、f 前移为 a、r、e |
are |
zpv |
z 前移为 y,p、v 前移为 o、u |
you |
拼接后得到 Hello ! How are you!,与样例输出完全一致。可以看到感叹号与两个连续空格都被原样保留,没有任何压缩或丢弃。
算法步骤
- 用
std::getline(Python 用sys.stdin.readline)整行读入密文字符串。 - 从左到右枚举字符串中的每一个字符
c。 - 若
c在b到z之间,把它替换为前一个字母。 - 否则若
c等于a,把它替换为z。 - 否则若
c在B到Z之间,把它替换为前一个字母。 - 否则若
c等于A,把它替换为Z。 - 其余字符不做任何改动。
- 输出处理后的整行。
复杂度分析
- 时间:
,其中
为字符串长度。每个字符只做常数次比较与一次赋值,长度上界为
。
- 空间:
,需要存下整行文本。若改为逐字符读入并立即输出,可以做到
,但整行保存的写法更简洁,
个字符的占用可以忽略。
实现注意事项
- 必须整行读入。若用
cin >>读std::string,读入会在第一个空白字符处停下,行内空格与后半行全部丢失,样例第一行就会得到错误结果。 - Python 读入后不要用
strip()去空白,它会同时删掉行首与行末的空格;只应去掉行尾的换行符,例如rstrip("\n")。 - 连续空格必须原样保留,不能对空白做任何压缩、合并或归一化处理。
- 大小写回绕分别进行:
a回绕到z,A回绕到Z,两者不可混用。 @属于非字母字符,按题面背景的更新说明,它必须原样输出,不能当作A处理。- 不要对所有字符统一做 ASCII 码减一,否则非字母字符会被改坏,
a也会变成反引号而非z。 - 长度不超过
,无论用下标还是迭代器遍历都不存在越界或整型溢出风险。
- 输入只有一行且以换行结尾;即使这一行为空,程序也应输出一个空行而不是出错。
源代码
#include <iostream>
#include <string>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string s;
std::getline(std::cin, s);
for (char &c : s) {
if (c >= 'b' && c <= 'z') {
c = c - 1; // 小写字母前移一位
} else if (c == 'a') {
c = 'z'; // a 回绕到 z
} else if (c >= 'B' && c <= 'Z') {
c = c - 1; // 大写字母前移一位
} else if (c == 'A') {
c = 'Z'; // A 回绕到 Z
}
// 其他非字母字符(空格、at 符号、标点等)原样保留
}
std::cout << s << '\n';
return 0;
}
import sys
def main():
s = sys.stdin.readline().rstrip("\n")
res = []
for ch in s:
if "b" <= ch <= "z":
res.append(chr(ord(ch) - 1)) # 小写字母前移一位
elif ch == "a":
res.append("z") # a 回绕到 z
elif "B" <= ch <= "Z":
res.append(chr(ord(ch) - 1)) # 大写字母前移一位
elif ch == "A":
res.append("Z") # A 回绕到 Z
else:
res.append(ch) # 其他非字母字符原样保留
sys.stdout.write("".join(res) + "\n")
main()
评论