题解:学而思编程 解密
【题目来源】
学而思编程:解密
【题目描述】
上一次,小猴的加密方式很快就被破解掉了,后来他学习了一个很经典的加密方式——“凯撒密码”,可是他觉得这个也很容易就会被别人破解,所以他决定创造一种“猴氏撒密码”。
和“凯撒密码”一样,“猴氏撒密码撒密码”也是利用字母向后偏移来实现的,但是他觉得偏移值如果固定的话也很容易被人破解,所以在加密后还会把这个字母再加上偏移值个数,例如 "\(a\)" 在偏移值为 \(3\) 时,会被加密为 "\(dddd\)"。如果保证明文中相邻字母加密后的字母不会相同,现在给出你一个加密后的结果,你能把明文找出来
【输入】
第一行一个仅由小写字母组成的的字符串 \(s\),表示加密后的结果。
【输出】
一个字符串表示加密前的明文。
【输入样例】
aaadddd
【输出样例】
ya
【核心思想】
-
问题分析:给定加密后的字符串,加密规则为:每个明文字母向后偏移 \(k\) 位(\(k\) 为偏移值),然后重复输出 \(k\) 次。例如
a偏移 \(3\) 变为d,再输出 \(3\) 次得到ddd。已知相邻明文字母加密后结果不同,求明文。这是一个连续段统计 + 逆向偏移问题,关键在于识别连续相同字符段并还原原始字符。 -
算法选择:
- 连续段分割:扫描字符串,将连续相同字符识别为一个加密块
- 逆向偏移:每个块的长度即为偏移值 \(k\),用该值逆向还原明文字母
-
关键步骤:
- 初始化:读取加密字符串 \(s[1..n]\)
- 扫描连续段:
k = 0:当前连续相同字符计数- 遍历 \(i\) 从 \(1\) 到 \(n\):
k++:当前字符计数加 \(1\)- 若
s[i] != s[i+1](当前字符与下一个不同,或到达末尾):- 还原明文字母:
- 偏移值 = \(k\)(连续出现次数)
- 明文字母 =
(s[i] - 'a' - (k-1) % 26 + 26) % 26 + 'a' - 解释:加密时向后偏移了 \((k-1)\) 位(因为偏移 \(1\) 时字符不变,重复 \(1\) 次),解密时向前还原
- 输出该字符
k = 0:重置计数器
- 还原明文字母:
- 输出完整明文
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),线性扫描字符串
- 空间复杂度:\(O(n)\),存储字符串
-
连续段分割的核心思想:
- 加密结构识别:由于相邻明文字母加密后结果不同,加密后的字符串天然形成"连续相同字符块"结构,每块对应一个明文字母
- 块长度即偏移值:块的长度 \(k\) 就是该字母的偏移参数,也是重复输出次数
- 偏移还原公式:加密时
ch = (原字符 + (k-1)) % 26,解密时原字符 = (ch - (k-1) + 26) % 26,加 \(26\) 防止负数 - 边界处理:
k-1而非 \(k\) 是因为偏移值从 \(1\) 开始(偏移 \(1\) 表示字符不变,输出 \(1\) 次) - 适用于"长度编码 + 字符偏移"类加密解密问题,连续段识别将密文分割为独立块
【算法标签】
字符串入门
【代码详解】
#include <bits/stdc++.h>
using namespace std;
char s[1000005];
int main()
{
cin >> s + 1; // 从下标1开始读取字符串
int n = strlen(s + 1);
int k = 0; // 记录当前连续相同字符的数量
for (int i = 1; i <= n; i++)
{
k++;
// 如果当前字符和下一个字符不同,或者到达字符串末尾
if (s[i] != s[i + 1])
{
// 还原原始字符:从加密字符减去连续的次数(取模26)
char ch = (s[i] - 'a' - (k - 1) % 26 + 26) % 26 + 'a';
cout << ch;
k = 0; // 重置计数器
}
}
return 0;
}
【运行结果】
aaadddd
ya
浙公网安备 33010602011771号