题解:学而思编程 Z字形加密
【题目来源】
学而思编程:Z字形加密
【题目描述】
在密码学中,我们称需要加密的信息为明文,用 \(M\) 表示;称加密后的信息为密文,用 \(C\) 表示;而密钥是一种参数,是将明文转换为密文或将密文转换为明文的算法中输入的数据,记为 \(k\)。
猴博士设计了一种加密算法:Z字形加密。其加密原理如下:
明文 \(M\) 是一个只包含大小字母和数字的字符串,将明文根据密钥 \(k\) 从上往下、从左到右进行Z字形排列成 \(k\) 行。
例如明文 \(M\) = "\(YouAreLucky\)",密钥 \(k = 3\) 时,排列如下:

然后把排列出的字符串从上往下、从左到右依连接成一个新的字符串,即为经过加密之后的密文。上述例子最终的密文为 "\(YrcoAeukuLy\)"。
【输入】
第一行一个字符串 \(M\),表示输入的明文信息;
第二行一个整数 \(k\),表示密钥参数。
【输出】
一行一个字符串 \(C\),表示经过加密之后的密文信息。
【输入样例】
YouAreLucky
3
【输出样例】
YrcoAeukuLy
【核心思想】
-
问题分析:给定明文字符串 \(M\) 和密钥 \(k\),将字符按 Z 字形(从上到下、再从下到上)排列成 \(k\) 行,然后按行优先顺序连接得到密文。这是一个方向模拟 + 分层存储问题,关键在于模拟 Z 字形排列过程,将字符按行分组存储后输出。
-
算法选择:
- 方向状态机:维护当前行索引 \(j\) 和移动方向
flag(向下/向上) - 分层存储:用 \(k\) 个向量分别存储每行的字符
- 边界转向:到达第 \(0\) 行或第 \(k-1\) 行时改变方向
- 方向状态机:维护当前行索引 \(j\) 和移动方向
-
关键步骤:
- 初始化:
- 读取明文 \(M\) 和密钥 \(k\)
n = M.size():字符串长度- 创建 \(k\) 个空向量
a[0..k-1] flag = 1:初始方向向下,j = 0:初始在第 \(0\) 行
- Z 字形排列(遍历 \(i\) 从 \(0\) 到 \(n-1\)):
a[j].push_back(M[i]):将当前字符放入第 \(j\) 行- 若
flag == 1(向下移动):- 若
j < k-1:j++(继续向下) - 否则:
j--,flag = 0(到达底部,转向向上)
- 若
- 否则(
flag == 0,向上移动):- 若
j > 0:j--(继续向上) - 否则:
j++,flag = 1(到达顶部,转向向下)
- 若
- 按行输出密文:
- 遍历 \(i\) 从 \(0\) 到 \(k-1\):
- 遍历
a[i]中所有字符并输出
- 遍历
- 遍历 \(i\) 从 \(0\) 到 \(k-1\):
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),每个字符处理一次,输出一次
- 空间复杂度:\(O(n)\),所有字符存储在 \(k\) 个向量中
-
Z 字形排列的核心思想:
- 周期性移动:行索引 \(j\) 在 \([0, k-1]\) 范围内呈周期性锯齿运动,周期为 \(2(k-1)\)(除 \(k=1\) 外)
- 边界反弹:到达第 \(0\) 行或第 \(k-1\) 行时方向反转,模拟 Z 字的折返点
- 行分组特性:同一行的字符在原字符串中的位置间隔为 \(2(k-1)\) 或交替为 \(2j\) 和 \(2(k-1-j)\),但模拟法无需推导公式
- 分层输出:按行优先连接时,每行内部字符保持原排列顺序,直接顺序输出即可
- 适用于"Z 字形/蛇形排列"类问题,方向模拟直观易懂,也可通过数学公式直接计算每个字符的目标位置实现 \(O(1)\) 空间优化
【算法标签】
字符串入门
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int main()
{
string M; // 输入的字符串
cin >> M;
int k; // 行数
cin >> k;
int n = M.size(); // 字符串长度
vector<char> a[k]; // 每行存储的字符
int flag = 1; // 方向标志:1向下,0向上
int i = 0; // 字符索引
int j = 0; // 行索引
while (i != n) // 遍历所有字符
{
a[j].push_back(M[i]); // 将字符放入对应行
if (flag % 2 == 1) // 向下移动
{
if (j != k - 1) // 如果不是最后一行
{
j++; // 向下移动
}
else // 到达最后一行
{
if (j != 0) // 如果不是第一行
{
j--; // 向上移动
flag = 0; // 改变方向
}
}
}
else // 向上移动
{
if (j != 0) // 如果不是第一行
{
j--; // 向上移动
}
else // 到达第一行
{
if (j != k - 1) // 如果不是最后一行
{
j++; // 向下移动
flag = 1; // 改变方向
}
}
}
i++; // 处理下一个字符
}
// 按行输出
for (int i = 0; i < k; i++)
{
for (int j = 0; j < a[i].size(); j++)
{
cout << a[i][j];
}
}
return 0;
}
【运行结果】
YouAreLucky
3
YrcoAeukuLy
浙公网安备 33010602011771号