题目
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:
P A H N A P L S I I G Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符如:"PAHNAPLSIIGYIR"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入:s = "PAYPALISHIRING", numRows = 3 输出:"PAHNAPLSIIGYIR"
示例 2:
输入:s = "PAYPALISHIRING", numRows = 4 输出:"PINALSIGYAHRPI" 解释:
P I N A L S I G Y A H R P I
示例 3:
输入:s = "A", numRows = 1 输出:"A"
提示:
1 <= s.length <= 1000 s 由英文字母(小写和大写)、',' 和 '.' 组成 1 <= numRows <= 1000
思路
首先看题目都看了半天,没明白z字形在哪?
最后才发现横着看才是z字形,如果竖着看的话其实是N字形
实现思路将原字符串转换成char[],然后重组这个数组的下标,最后根据重组的下标拼成新的字符串输出,可以按照上边给出的示例将整个输出拆成相同的单元
L D R E O E I I E C I H N T S G
- 第一行和最后一行相邻间隔:【2 * numRows - 2】
- 其他行的相邻间隔:【2 * (numRows - i) 】或 【2 * 当前行 - 2】,两个公式交替使用;
其实逐行来看,除了第一行和最后一行,每一行的两个单元之间都存在一个字母,这时我们把这个字母左边的空间称为leftSpace右边的空间称为rightSpace,从第二行开始,这行的所有下标就是从1开始,以leftSpace和rightSpace交替增加得到所有下标第一行我们也可以写成leftSpace和rightSpace交替增加的样子,但是因为第一行rightSpace为0,所以会出现两次相同的结果,这时需要特殊处理下,只记一次
public String convert(String s, int numRows) { //此时不需要做转换 if (numRows <= 1) { return s; } char[] data = s.toCharArray(); int[] resultIndex = new int[data.length]; //每个单元之间间隔字母的左空间长度 int leftSpace = 2 * numRows - 2; //每个单元之间间隔字母的右空间长度 int rightSpace = 0; for (int i = 0, index = 0; i < numRows && index < data.length; i++) { int max = i; //用于控制左右空间交替相加的开关 boolean flag = true; //用于比较前后添加的两个下标是否相同,用来处理第一行和最后一行没有间隔字母导致会添加两次相同下标的问题 int lastAdd = i; //每行的开头固定的为(行号-1) resultIndex[index] = i; index++; //控制每行最大的索引不要超 while (data.length - 1 >= (flag ? max + (leftSpace - i * 2) : max + (rightSpace + i * 2))) { if (flag) { max = max + (leftSpace - i * 2); flag = false; } else { max = max + (rightSpace + i * 2); flag = true; } if (max != lastAdd) { resultIndex[index] = max; index++; lastAdd = max; } } } StringBuilder sb = new StringBuilder(); for (int i = 0; i < resultIndex.length; i++) { sb.append(data[resultIndex[i]]); } return sb.toString(); }
执行用时:2 ms, 在所有 Java 提交中击败了99.99%的用户
内存消耗:38.1 MB, 在所有 Java 提交中击败了98.86%的用户
另一种解法
上面的解法主要是通过分成多组找下表规律后再拼接字符串,其实分成左右区间的规律不是特别好找,实现也比较繁琐。
接下来我们再仔细观察下按顺序遍历字符串 s 时,每个字符 c 在Z字形的行索引先递增到最大,然后再到最小,一直反复指导结束
也就是说在打印出Z字形顺序的时候,字符的行号是0->max->0->max....->end变化的
所以可以通过模拟打印Z字形过程,把每行数据存储起来,最后按照行号输出结果拼接成字符串就是最终要的的结果
public static String convert2(String s, int numRows) { if (numRows < 2) { return s; } //根据入参行数,构造每一行存储StringBuilder用于保存每一行数据 StringBuilder[] rows = new StringBuilder[numRows]; Arrays.setAll(rows, i -> new StringBuilder()); int row = 0, flag = -1; for (int j = 0; j < s.length(); j++) { //追加到第n行 rows[row].append(s.charAt(j)); //如果行数为0或者为最大,此时需要翻转标志位 //翻转的目的是下面row的继续遍历能有统一的+=flag if (row == 0 || row == numRows - 1) { flag = -flag; } row += flag; } return String.join("", rows); }
执行用时:8 ms, 在所有 Java 提交中击败了34.18%的用户
内存消耗:38.9 MB, 在所有 Java 提交中击败了50.74%的用户
虽然效率有所降低,但是可读性和可理解性大大提高,同时这种思路也更符合人的心智模型,更容易记住,所以个人认为如果遇到这道题,答出这种答案就能够满足要求。
今天多学一点,明天就少说一句求人的话