您的位置:首页 > 编程语言 > C语言/C++

6. ZigZag Conversion( Z 字形变换)两种解题方法(C++ & 注释)

2020-07-14 05:29 561 查看

6. ZigZag Conversion( Z 字形变换)

  • 3. Visit by Row(按行访问)
  • 1. 题目描述

    将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

    比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:

    L C I R
    E T O E S I I G
    E D H N

    之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。

    请你实现这个将字符串进行指定行数变换的函数:

    string convert(string s, int numRows);

    示例 1:

    输入: s = “LEETCODEISHIRING”, numRows = 3 输出: “LCIRETOESIIGEDHN”

    示例 2:

    输入: s = “LEETCODEISHIRING”, numRows = 4 输出: “LDREOEIIECIHNTSG”

    解释:

    L D R
    E O E I I
    E C I H N
    T S G

    题目链接:中文题目英文题目

    2. Sort by Row(按行排序)

    2.1 解题思路

    按行排序的思路其实非常简单易懂:我们可以看到Zigzag后的字符串其实是元字符串从第一个字符开始,先竖向往下排序,当行数到达第numRows - 1行后,后退一行,并向上开始排序,之后行数到达第0行后,前进一行,重新开始向下开始排序,这题思路如下图所示:

    理解上述思路以后,代码就显得非常简单了,并不难。

    2.2 实例代码

    class Solution {
    public:
    string convert(string s, int numRows) {
    if (numRows <= 1) return s;
    vector<string> zigzag(min(numRows, static_cast<int>(s.size()))); // 需考虑到s的长度为零的情况
    int curRow = 0;
    bool goDown = false;
    
    for (int i = 0; i < s.size(); i++) {
    zigzag[curRow] += s[i]; // 添加当前行的字符
    if (curRow == 0 || curRow == numRows - 1) goDown = !goDown; // 如果当前行位于收尾需要转换方向
    curRow += goDown ? 1 : -1; // goDown为真,访问下一行;反之,访问上一行
    }
    
    string ans("");
    for (string& str : zigzag) ans += str;
    return ans;
    }
    };

    3. Visit by Row(按行访问)

    3.1 解题思路

    除了按照行排序,那我们能不能直接计算出竖行和斜行分别对应原字符串序号的规律呢?答案当然是OK哒,我们先来看看几个例子,然后从里面总结出规律:

    从图中,我们可以看到斜行的字符个数与numRows的关系为:numRows > 2 ? numRows - 2 : 0;接着,我们可以观察到首行尾行和中间行有着不同的规律,我们以numRows = 3为例。


    1)首行和尾行:
    首行的字符为:a, e, i,对应序号0, 1, 8;尾行字符为:c, g, k,对应序号2, 6, 10。我们可以发现每个序号的差值刚好为4,即首行和尾行的上一个字符与下一个字符的序号差值为2 * numRows - 2,这个公式怎么得到的呢?我们可以观察一下原字符串,a走到下一个字符e需要跳过那些字符呢?首先我们要走到c,也就是走到竖行字符的最后一个,走过的字符数为:numRows - 1,然后我们需要走完斜行的所有字符,即numRows - 2个字符,然后再走一步,达到e字符,所以一共跳过的序号为:numRows - 1 + numRows - 2 + 1 = 2 * numRows - 2;

    2)中间行
    中间行的首字符序号为1 ~ numRows - 1,也就是第一个竖行除去首尾的字符。之后先访问斜行对称的字符,比如numRows = 4时,bc对应ef,先访问b然后访问斜行最后一个字符f,刚好是竖行中间与斜行放在一起,两者对称位置上的字符。然后访问下一个竖行对应位置的字符,之后重复上述动作直到序号越界就停止。我们观察发现从竖行到下一个竖行的移动距离和上面首尾行的是一样的,都为2 * numRows - 2。但是从竖行的字符到对应斜行的字符距离为numRows - (i + 1) + numRows - 2 - i + 1,i为第一个竖行字符的序号。

    那这个距离公式怎么得到的呢?还是numRows = 3时,首先从b到d,b的序号为1,b之前(包含自己)一共有i + 1个字符,所以需要跳过b之后的字符数为numRows - (i + 1)个,然后我们要跳过斜行的字符,斜行有numRows - 2个字符,我们需要计算与竖行对称的字符之前有多少个字符,只需要用斜行的字符个数减去i所在竖行后面的字符数(除去首尾)即numRows - 2 - i + 1个

    最后我们只需遍历0 ~ numRows - 1序号的字符就可以得到答案了。其实代码里面两个函数可以合成到一起,毕竟跳到下一个竖行字符,首尾和中间行都是2 * numRows - 2,大家可以自己尝试合并一下呢。

    3.2 实例代码

    class Solution {
    public:
    string convert(string s, int numRows) {
    if (numRows <= 1) return s;
    string ans("");
    for (int i = 0; i < numRows; i++) {
    if (i == 0 || i == numRows - 1) addCharacterAtBoundary(s, ans, numRows, i); // 添加收尾两行字符
    else addCharaceterBetween(s, ans, numRows, i); // 添加中间行字符
    }
    
    return ans;
    }
    
    void addCharacterAtBoundary(string& s, string& ans, int numRows, int i) {
    int jumpTo = 2 * numRows - 2; // 跳到下一个竖边的对应字符
    while (i < s.size()) {
    ans.push_back(s[i]);
    i += jumpTo;
    }
    }
    
    void addCharaceterBetween(string& s, string& ans, int numRows, int i) {
    int numberOfZigzag = numRows - 2 - i + 1, // 计算中间斜边上字符的总数
    len = s.size(),
    jumpToZigzag = numRows - (i + 1) + numberOfZigzag, // 跳到下一个斜边的对应字符
    jumpToNext = 2 * numRows - 2;
    while (i < len) {
    //cout << i  << jumpToZigzag << " ";
    ans.push_back(s[i]);
    if (i + jumpToZigzag >= len) break;
    ans.push_back(s[i + jumpToZigzag]);
    i += jumpToNext;
    }
    }
    };

    如果大家有任何的问题,欢迎在下方留言,我看到以后会及时回复大家哒~

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐