题目
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入: s = “LEETCODEISHIRING”, numRows = 3
输出: “LCIRETOESIIGEDHN”
示例 2:
输入: s = “LEETCODEISHIRING”, numRows = 4
输出: “LDREOEIIECIHNTSG”
解释:
题目解析及优化
-
我自己愿意称下面的方法为暴力法
既然想要将字符串转变成为Z形输出,那就用一个二维数组,将字符串依次填到对应位置上,然后按行进行输出即可。直接上代码:
class Solution { public: string convert(string s, int numRows) { char arr[numRows][500]; //初始化二维数组 for (int i = 0; i < numRows; i++) { for (int j = 0; j < 500; j++) { arr[i][j] = '#'; } } //设置每个循环的Z多少个字符(T),横向占多少行(line) int T = numRows == 1 ? 1 : 2 * numRows - 2; int line = numRows == 1 ? 1 : 1 + numRows - 2; for (int i = 0; i < s.size(); i++) { int n = i / T; int pos = i % T + 1; if (pos > numRows) { arr[2 * numRows - pos - 1][n * line + pos - numRows] = s[i]; } else { arr[pos - 1][n * line] = s[i]; } } string ans; //逐行扫描添加到答案中 for (int i = 0; i < numRows; i++) { for (int j = 0; j < 500; j++) { if (arr[i][j] != '#') { ans.push_back(arr[i][j]); } } } return ans; } };
对这种解法还能做进一步的优化。‘#’字符只起到了方便观看的作用,而没有实际用途。对字符串s逐步遍历的时候,直接找出应该跟在哪一行后即可。优化之后代码如下:
class Solution { public: string convert(string s, int numRows) { if (numRows == 1)return s; string ans; vector <string> result(numRows); bool flag = true; int line = 0; for (int i = 0; i < s.size(); i++) { result[line].push_back(s[i]); if ((line == 0 || line == numRows - 1) && i > 0) { flag = !flag; } line += flag ? 1 : -1; } for (int i = 0; i < numRows; i++) { ans = ans + result[i]; } return ans; } };
-
直接输出
其实在写第一种方法途中找每个字符在二维数组中的位置,就基本可以想到第二种方法,对初始字符串最直接处理并输出。第一种方法是从初始字符串找每个字符在新字符串中的位置,第二种是从目标字符串思考,每个字符串来自原来字符串的上面地方。
第0行的字符位于索引的k(2*numRows-2)处。
第numRows-1行的字符位于(2*numRows - 2) + numRow-1处
中间的行位于 (2 * numRows-2) + i 和(2 * numRows -2) -i处
以上的坐标为第一个周期内,其余的乘倍数即可
代码如下:
class Solution { public: string convert(string s, int numRows) { if (numRows == 1) { return s; } string ans; int n = s.size(); int T = 2 * numRows - 2; for (int i = 0; i < numRows; i++) { for (int j = 0; i + j < n; j += T) { ans.push_back(s[i + j]); //在中间行时额外输一次 if (i > 0 && i < (numRows - 1) && j + T - i < n) { ans.push_back(s[j + T - i]); } } } return ans; } };
心得及总结
- 看到题目最先想到的应该是二维数组去遍历,但是用二维数组会浪费一些空间,此时可以根据字符串遍历的特征去改变策略。拿到题目有基本的做法时多想一步,思考这种做法的本质去优化;
- 在每一次循环的时候输出次数不一样时,将比较另类的判断并输出;
- 在第一种方法的优化过程中,判断转换方向的时候忘记了行数等于0的时候,解决方法除了增加转换条件还可以直接将方向改为向上也可以;