题目描述(力扣题库6):
将一个给定字符串 s
根据给定的行数 numRows
,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING"
行数为 3
时,排列如下:
P A H N A P L S I I G PAHN APLSIIG YIR Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"
。
解释如图:
示例 :
输入:s = "PAYPALISHIRING", numRows = 3 输出:"PAHNAPLSIIGYIR"
提示:
1 <= s.length <= 1000
s
由英文字母(小写和大写)、','
和'.'
组成1 <= numRows <= 1000
法一:
- 创建一个长度为 numRows 的
StringBuilder
数组 sb,每个元素代表 Z 字形中的一行。 - 遍历输入字符串中的每个字符,根据 Z 字形规则将字符添加到相应的行中。
- 以一个指针 x 来表示当前字符应该被添加到哪一行,初始时 x = 0。
- 通过维护一个变量 timeSum = 2 * numRows - 2,可以确定字符在 Z 字形中的行数变化规律。
- timeSum为一个周期内(仔细观察 z 字形排序, 不难发现它是有规律可循的),元素的个数
- 观察上图, 不难发现每个周期有四个元素( numRows + (numRows - 2 ) )
- 继续仔细观察上图, 不难发现, 当 i % timeSum < numRows - 1 时,x 递增;否则 x 递减。
- 将所有行的
StringBuilder
按顺序连接起来,形成最终的结果字符串。 - 返回最终结果字符串。
整体算法的时间复杂度为 O(n),其中 n 是输入字符串的长度。
具体步骤如下:
-
初始化检查:
- 首先检查 numRows 的值是否小于 2 或者大于输入字符串的长度,如果是,则直接返回原始字符串。
-
if(numRows < 2 || numRows > str.length()) return str;
-
2.初始化StringBuilder数组:
- 创建一个长度为 numRows 的
StringBuilder
数组sb
,用于存储每行的字符。 - 使用 for 循环初始化每行对应的
StringBuilder
对象。
StringBuilder[] sb = new StringBuilder[numRows];
for (int i = 0; i < numRows; i++) {
sb[i] = new StringBuilder();
}
3.按 Z 字形将字符添加到对应的行:
- 使用循环遍历输入字符串中的每个字符。
- 根据 Z 字形的规律,依次将字符添加到
sb
数组中的对应行。 - 使用变量
x
来表示当前行的索引,timeSum
表示 Z 字形一个完整的周期长度。 - 当
i % timeSum < numRows - 1
时,向下移动到下一行;否则向上移动到上一行。-
for(int i = 0, x = 0, timeSum = 2 * numRows - 2; i < str.length(); i++){ sb[x].append(str.charAt(i)); if(i % timeSum < numRows - 1) x++; else x--; }
-
4.连接各行的StringBuilder:
- 创建一个新的
StringBuilder
对象ans
,用于存储最终的结果。 - 使用循环将
sb
数组中的每一行的内容依次添加到ans
中。 - 返回结果
-
StringBuilder ans = new StringBuilder(); for(StringBuilder row : sb){ ans.append(row); } return ans.toString();
-
-
代码到这里已经结束了,这个解法的思路与官方题解相差不大,但是我将代码优化了一下,一是我将变量名修改了一下,便于理解.二是我使用了StringBuilder代替StringBuffer(代码运行时间从8ms 减少到了 4ms):StringBuilder比StringBuffer更轻量级,因此在没有多线程安全需求的情况下,建议使用StringBuilder。以下是完整的代码
class Solution {
public static String convert(String str, int numRows){
if(numRows < 2 || numRows > str.length())
return str;
StringBuilder[] sb = new StringBuilder[numRows];
for (int i = 0; i < numRows; i++) {
sb[i] = new StringBuilder();
}
for(int i = 0, x = 0, timeSum = 2 * numRows - 2; i < str.length(); i++){
sb[x].append(str.charAt(i));
if(i % timeSum < numRows - 1)
x++;
else
x--;
}
StringBuilder ans = new StringBuilder();
for(StringBuilder row : sb){
ans.append(row);
}
return ans.toString();
}
}
- 以下是该代码的原理-图形解释:
法二: 在法一的基础上进行了优化(耗时由4ms -- 2ms)
- 避免使用二维字符数组:在给定代码中,使用了一个二维字符数组来存储转换后的字符矩阵。这样做会占用额外的空间,并且需要进行额外的遍历来构建最终的字符串。可以通过直接使用StringBuilder来构建最终的字符串,而不需要使用二维字符数组。
-
简化计算行数的逻辑:在给定代码中,计算行数的逻辑可以简化。当行数为1或大于等于字符串长度时,直接返回原始字符串。可以使用Math.min(r, n)来简化计算行数的逻辑。
这里是对优化后的代码的每一个步骤的解释:
1. 条件判断:
- `if (numRows < 2 || numRows >= str.length()) { return str; }`:首先检查行数是否小于2或者大于等于输入字符串的长度,如果是,则直接返回原始字符串,因为在这些情况下不需要进行ZigZag转换。
2. StringBuilder初始化和循环填充字符:
- `StringBuilder ans = new StringBuilder();`:创建一个StringBuilder对象`ans`用于存储最终的转换结果。
- `int cycleLen = 2 * numRows - 2;`:计算ZigZag模式中一个完整循环的长度。
- `for (int i = 0; i < numRows; i++) {`:遍历每一行。
- `for (int j = i; j < str.length(); j += cycleLen) {`:在每一行中,根据ZigZag规律填充字符。
- `ans.append(str.charAt(j));`:将当前行的字符添加到StringBuilder中。
- `int next = j + cycleLen - 2 * i;`:计算下一个需要添加的字符的索引。
- `if (i != 0 && i != numRows - 1 && next < str.length()) { ans.append(str.charAt(next)); }`:如果不是第一行和最后一行,并且下一个字符的索引在字符串范围内,则添加下一个字符。
3. 返回结果:
- `return ans.toString();`:将StringBuilder转换为字符串并返回。
通过这些步骤,优化后的代码实现了在ZigZag模式下对输入字符串进行转换,并且避免了使用额外的数据结构,使得代码更加简洁和高效。
- 以下是完整的代码:
-
class Solution { public String convert(String str, int numRows) { // 如果行数小于2或大于等于字符串长度,则直接返回原始字符串 if (numRows < 2 || numRows >= str.length()) { return str; } StringBuilder ans = new StringBuilder(); int cycleLen = 2 * numRows - 2; // 计算周期长度 // 遍历每一行 for (int i = 0; i < numRows; i++) { // 按照ZigZag模式填充字符 for (int j = i; j < str.length(); j += cycleLen) { ans.append(str.charAt(j)); // 填充当前行的字符 int next = j + cycleLen - 2 * i; // 对于非首行和末行,填充额外的字符 if (i != 0 && i != numRows - 1 && next < str.length()) { ans.append(str.charAt(next)); } } } return ans.toString(); } }
-
以上就是本篇文章的全部内容,该博客文字讲述偏多,掺杂了博主自己的想法与见解,内容如若有不妥之处,敬请谅解.