题解:学而思编程 Z字形加密

【题目来源】

学而思编程:Z字形加密

【题目描述】

在密码学中,我们称需要加密的信息为明文,用 \(M\) 表示;称加密后的信息为密文,用 \(C\) 表示;而密钥是一种参数,是将明文转换为密文或将密文转换为明文的算法中输入的数据,记为 \(k\)

猴博士设计了一种加密算法:Z字形加密。其加密原理如下:

明文 \(M\) 是一个只包含大小字母和数字的字符串,将明文根据密钥 \(k\) 从上往下、从左到右进行Z字形排列成 \(k\) 行。

例如明文 \(M\) = "\(YouAreLucky\)",密钥 \(k = 3\) 时,排列如下:

然后把排列出的字符串从上往下、从左到右依连接成一个新的字符串,即为经过加密之后的密文。上述例子最终的密文为 "\(YrcoAeukuLy\)"。

【输入】

第一行一个字符串 \(M\),表示输入的明文信息;

第二行一个整数 \(k\),表示密钥参数。

【输出】

一行一个字符串 \(C\),表示经过加密之后的密文信息。

【输入样例】

YouAreLucky
3

【输出样例】

YrcoAeukuLy

【核心思想】

  1. 问题分析:给定明文字符串 \(M\) 和密钥 \(k\),将字符按 Z 字形(从上到下、再从下到上)排列成 \(k\) 行,然后按行优先顺序连接得到密文。这是一个方向模拟 + 分层存储问题,关键在于模拟 Z 字形排列过程,将字符按行分组存储后输出。

  2. 算法选择

    • 方向状态机:维护当前行索引 \(j\) 和移动方向 flag(向下/向上)
    • 分层存储:用 \(k\) 个向量分别存储每行的字符
    • 边界转向:到达第 \(0\) 行或第 \(k-1\) 行时改变方向
  3. 关键步骤

    • 初始化
      • 读取明文 \(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-1j++(继续向下)
        • 否则:j--flag = 0(到达底部,转向向上)
      • 否则(flag == 0,向上移动):
        • j > 0j--(继续向上)
        • 否则:j++flag = 1(到达顶部,转向向下)
    • 按行输出密文
      • 遍历 \(i\)\(0\)\(k-1\)
        • 遍历 a[i] 中所有字符并输出
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),每个字符处理一次,输出一次
    • 空间复杂度:\(O(n)\),所有字符存储在 \(k\) 个向量中
  5. 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
posted @ 2026-07-04 15:03  团爸讲算法  阅读(6)  评论(0)    收藏  举报