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

比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下: :::info P A H N
A P L S I I G
Y I R ::: 之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,”PAHNAPLSIIGYIR”。

请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);

方法一:利用二维矩阵模拟

设Len为字符串s的长度。

当numRows=1或者完成Z字变换之后只有一列(numRows>Len)时,结果与原字符串相同,输出原结果即可。
对于一般情况,建立二维数组,将字符串按Z字变换顺序填入二维数组,然后再按行输出二维数组。

由题意得,输入字符时,会先向下输出numRows个字符,再向右上输出(numRows-2)个字符,并形成输入周期cycle=numRows+(numRows-2)=2numRows-2。
每周期会占用列数column=1+(numRows-2)=numRows-1列。

又已知字符串长度Len,即可获得字符串输入周期数Len/cycle,同时*column,即可获得二维数组的列数。

创建一个Len行column列的矩阵,然后遍历填写。初始位置在矩阵的左上角,记为(x,y)=(0,0)。当字符满足i mod t < numRows-1,向下移动,否则向右上移动。
最后逐行输出非空字符。

  1. public class Solution
  2. {
  3. public string Convert(string s, int numRows)
  4. {
  5. int Len = s.Length;
  6. // 排除特殊情况
  7. if (numRows == 1 || numRows >= Len)
  8. {
  9. return s;
  10. }
  11. int cycle = numRows * 2 - 2;// 循环的周期
  12. int column = (Len + cycle - 1) / cycle * (numRows - 1);
  13. // 组建二维数组的列数
  14. // 为方便计算,将最后一个周期算作一个完整周期(不论周期是否完整)(Len + cycle - 1)
  15. char[][] mat = new char[numRows][];
  16. // 二维数组每行转化为一个一维数组
  17. for (int i = 0; i < numRows; ++i)
  18. {
  19. mat[i] = new char[column];
  20. }
  21. // 移动二维数组角标
  22. for (int i = 1, x = 0, y = 0; i < Len; i++)
  23. {
  24. mat[x][y] = s[i];
  25. if (i % cycle < numRows - 1)
  26. {
  27. ++x; // 向下移动
  28. }
  29. else
  30. {
  31. --x;
  32. ++y; // 向右上移动
  33. }
  34. }
  35. // 循环打印输出
  36. StringBuilder ans = new StringBuilder();
  37. foreach (char[] row in mat)
  38. {
  39. foreach (char ch in row)
  40. {
  41. if (ch != 0)
  42. {
  43. ans.Append(ch);
  44. }
  45. }
  46. }
  47. return ans.ToString();
  48. }
  49. }

方法二:压缩矩阵空间

方法一中的矩阵有大量的空间没有被使用,能否优化呢?

注意到每次往矩阵的某一行添加字符时,都会添加到该行上一个字符的右侧,且最后组成答案时只会用到每行的非空字符。因此我们可以将矩阵的每行初始化为一个空列表,每次向某一行添加字符时,添加到该行的列表末尾即可。

  1. public class Solution
  2. {
  3. public string Convert(string s, int numRows)
  4. {
  5. int Len = s.Length;
  6. if (numRows == 1 || numRows >= Len)
  7. {
  8. return s;
  9. }
  10. StringBuilder[] mat = new StringBuilder[numRows];
  11. for (int i = 1; i < numRows; i++)
  12. {
  13. mat[i] = new StringBuilder();
  14. }
  15. for (int i = 1, x = 0, cycle = numRows * 2 - 2; i < Len; i++)
  16. {
  17. mat[x].Append(s[i]);
  18. if (i % cycle < numRows - 1)
  19. {
  20. ++x;
  21. }
  22. else
  23. {
  24. --x;
  25. }
  26. }
  27. StringBuilder ans = new StringBuilder();
  28. foreach (StringBuilder row in mat)
  29. {
  30. ans.Append(row);
  31. }
  32. return ans.ToString();
  33. }
  34. }