【数据结构与算法】之深入解析“Z字形变换”的求解思路和算法示例

举报
Serendipity·y 发表于 2022/02/16 23:35:41 2022/02/16
【摘要】 一、题目要求 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下: P A...

一、题目要求

  • 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
  • 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
P   A   H   N
A P L S I I G
Y   I   R

  
 
  • 1
  • 2
  • 3
  • 之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。
  • 请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);

  
 
  • 1
  • 示例 1:
输入:s = "PAYPALISHIRING", numRows = 3
输出:"PAHNAPLSIIGYIR"

  
 
  • 1
  • 2
  • 示例 2:
输入:s = "PAYPALISHIRING", numRows = 4
输出:"PINALSIGYAHRPI"
解释:
P     I    N
A   L S  I G
Y A   H R
P     I

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 示例 3:
输入:s = "A", numRows = 1
输出:"A"

  
 
  • 1
  • 2
  • 提示:
    • 1 <= s.length <= 1000;
    • s 由英文字母(小写和大写)、’,’ 和 ‘.’ 组成;
    • 1 <= numRows <= 1000。

二、求解算法

① 按行寻找规律

  • 这个 Z 字型其实是这样的:

在这里插入图片描述

  • 对于前面的 3 行的 示例1,它的字符数分布是这样的:

在这里插入图片描述

  • 对于前面的 4 行的 示例2,它的字符数分布是这样的:

在这里插入图片描述

  • 那么对于 n 行的字符数分布是这样的:

在这里插入图片描述

  • 可以发现:
    • 当前行 curRow 为 0 或 n-1 时,箭头发生反向转折
      • 从左到右按箭头方向迭代 s ,将每个字符添加到合适的行,之后从上到下遍历行即可。
      • 假定 n=numRows,C++ 示例如下:
class Solution {
public:
	string convert(string s, int numRows) {

		if (numRows == 1) return s;

		vector<string> rows(min(numRows, int(s.size()))); // 防止s的长度小于行数
		int curRow = 0;
		bool goingDown = false;

		for (char c : s) {
			rows[curRow] += c;
			if (curRow == 0 || curRow == numRows - 1) {// 当前行curRow为0或numRows -1时,箭头发生反向转折
				goingDown = !goingDown;
			}
			curRow += goingDown ? 1 : -1;
		}

		string ret;
		for (string row : rows) {// 从上到下遍历行
			ret += row;
		}

		return ret;
	}
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
      • 比如对于 s = “LEETCODEISHIRING” ,得到的四行就是:

在这里插入图片描述

    • 每一行字母的所有下标其实是有规则的
      • 先假定有 numRows=4 行来推导下,其中 2numRows-2 = 6 , 我们可以假定为 step=2numRows-2 ,先来推导下规则:
        • 第 0 行: 0 - 6 - 12 - 18:==> 下标间距 6 - 6 - 6 ==> 下标间距 step - step - step
        • 第 1 行: 1 - 5 - 7 - 11 - 13:==> 下标间距 4 - 2 - 4 - 2 ==> 下标间距step-21(行)-21(行)-step-21(行)-21(行)
        • 第 2 行: 2 - 4 - 8 - 10 - 14:==> 下标间距 2 - 4 - 2 - 4 ==> 下标间距step-22(行)-22(行)-step-22(行)-22(行)
        • 第 3 行:3 - 9 - 15 - 21:==> 下标间距间距 6 - 6 - 6 ==>下标间距step - step - step
      • 可以得出以下结论:
        • 起始下标都是行号;
        • 第 0 层和第 numRows-1 层的下标间距总是 step;
        • 中间层的下标间距总是 step-2行数,2行数交替;
        • 下标不能超过 len(s)-1。
      • C++ 示例:
	string convert(string s, int numRows) {
		if (numRows == 1) return s;

		int step = numRows * 2 - 2; // 间距
		int index = 0;// 记录s的下标
		int len = s.length();
		int add = 0; // 真实的间距
		string ret;
		for (int i = 0; i < numRows; i++) // i表示行号
		{
			index = i;
			add = i * 2;
			while (index < len)//超出字符串长度计算下一层
			{
				ret += s[index]; // 当前行的第一个字母
				add = step - add;// 第一次间距是step -2*i,第二次是2*i, 
				index += (i == 0 || i == numRows-1) ? step : add; // 0行和最后一行使用step间距,其余使用add间距
			}
		}
		return ret;
	}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

② 按行排序(LeetCode 官方解法)

  • 通过从左向右迭代字符串,可以轻松地确定字符位于 Z 字形图案中的哪一行。
  • 我们可以使用 min(numRows,len(s)) 个列表来表示 Z 字形图案中的非空行。
  • 从左到右迭代 s,将每个字符添加到合适的行。可以使用当前行和当前方向这两个变量对合适的行进行跟踪。
  • 只有当我们向上移动到最上面的行或向下移动到最下面的行时,当前方向才会发生改变。
  • C++ 示例:
class Solution {
public:
    string convert(string s, int numRows) {

        if (numRows == 1) return s;

        vector<string> rows(min(numRows, int(s.size())));
        int curRow = 0;
        bool goingDown = false;

        for (char c : s) {
            rows[curRow] += c;
            if (curRow == 0 || curRow == numRows - 1) goingDown = !goingDown;
            curRow += goingDown ? 1 : -1;
        }

        string ret;
        for (string row : rows) ret += row;
        return ret;
    }
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • Java 示例:
class Solution {
    public String convert(String s, int numRows) {

        if (numRows == 1) return s;

        List<StringBuilder> rows = new ArrayList<>();
        for (int i = 0; i < Math.min(numRows, s.length()); i++)
            rows.add(new StringBuilder());

        int curRow = 0;
        boolean goingDown = false;

        for (char c : s.toCharArray()) {
            rows.get(curRow).append(c);
            if (curRow == 0 || curRow == numRows - 1) goingDown = !goingDown;
            curRow += goingDown ? 1 : -1;
        }

        StringBuilder ret = new StringBuilder();
        for (StringBuilder row : rows) ret.append(row);
        return ret.toString();
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 复杂度分析:
    • 时间复杂度:O(n),其中 n==len(s);
    • 空间复杂度:O(n)。

③ 按行访问(LeetCode 官方解法)

  • 按照与逐行读取 Z 字形图案相同的顺序访问字符串。
  • 首先访问 行 0 中的所有字符,接着访问 行 1,然后 行 2,依此类推…
  • 对于所有整数 k:
    • 行 0 中的字符位于索引 k(2⋅numRows−2) 处;
    • 行 numRows−1 中的字符位于索引 k(2⋅numRows−2)+numRows−1 处;
    • 内部的行 i 中的字符位于索引 k(2⋅numRows−2)+i 以及 (k+1)(2⋅numRows−2)−I 处;
  • C++ 示例:
class Solution {
public:
    string convert(string s, int numRows) {

        if (numRows == 1) return s;

        string ret;
        int n = s.size();
        int cycleLen = 2 * numRows - 2;

        for (int i = 0; i < numRows; i++) {
            for (int j = 0; j + i < n; j += cycleLen) {
                ret += s[j + i];
                if (i != 0 && i != numRows - 1 && j + cycleLen - i < n)
                    ret += s[j + cycleLen - i];
            }
        }
        return ret;
    }
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • Java 示例:
class Solution {
    public String convert(String s, int numRows) {

        if (numRows == 1) return s;

        StringBuilder ret = new StringBuilder();
        int n = s.length();
        int cycleLen = 2 * numRows - 2;

        for (int i = 0; i < numRows; i++) {
            for (int j = 0; j + i < n; j += cycleLen) {
                ret.append(s.charAt(j + i));
                if (i != 0 && i != numRows - 1 && j + cycleLen - i < n)
                    ret.append(s.charAt(j + cycleLen - i));
            }
        }
        return ret.toString();
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 复杂度分析:
    • 时间复杂度:O(n),其中 n==len(s)。每个索引被访问一次。
    • 空间复杂度:O(n),对于 C++ 实现,如果返回字符串不被视为额外空间,则复杂度为 O(1)。

文章来源: blog.csdn.net,作者:Serendipity·y,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/Forever_wj/article/details/122473690

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。