Java算法题:Z字形变换
将一个给定字符串 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
来源:力扣(LeetCode)
链接
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题目解析
这个Z字形意思就是将字符串进行单个字符逐一拆开,然后排列成一个向左边旋转90度的Z字
举个例子:
// 比如字符串 “ABCDEFGHIJKL” 行数是 3 这里的 - 代表空字符串
A - E - I -
B D F H J L
C - G - K -
// 比如字符串 “ABCDEFGHIJKL” 行数是 4 这里的 - 代表空字符串
A - - G - -
B - F H - L
C E - I K -
D - - J - -
题目要求是这样排列之后 从上往下、 从左往右依次重组字符串获得新的字符串那么行数为 3 的就变成了“AEIBDFHJLCGK”,而行数为 4 的 就变成了 “AGBFHLCEIKDJ”了
分析
由此我们可以进行一下分析
当我们在二维矩阵上填写字符的时候,会向下填写 r
个字符,然后向右上继续填写r − 2
个字符,最后回到第一行,因此
- Z 字形变换的周期
t = r + r - 2 = 2 * r - 2
- 而每一个周期需要占用
1 + r - 2
列
可以看到上面的例子代入公式进行计算
行数为 3 的周期为 3 + 3 - 2 = 4
,可以看到确实是每个周期需要 4 个字符,而每个周期需要占用 1 + 3 - 2 = 2
列,也对应上了
那么行数为 4 的就由大家自己动手算一算是否正确吧
接着往下讲
得到这些规律后
我们就需要进行进一步分析:
我们模拟一个二维矩阵进行排列,那么我们需要知道的是这个二维矩阵的行列数是多少
首先行数其实我们已经知道了,就是 numRows
,那么列数该怎么去求呢
首先我们需要知道这个字符串的长度,然后根据周期去求这个列数
字符串的长度可以通过 s.length()去求得
那么用长度去除以周期可以求得一共有多少个周期
再用周期数 * 每个周期占用的列数就可以得出这个二维矩阵一共有多少列了
但是要注意最后一个周期或许不是满的,这样计算出来可能会少了一个周期,所以我们使用把最后一个周期当做完整的周期去计算
列数 c = (n + t - 1) / t * (r - 1)
有了这么多信息,我们就可以写代码实现了
方法一:模拟二维数组
public String convert(String s, int numRows) {
// n 是 该字符串的长度, r 为行数
int n = s.length(), r = numRows;
// 如果行数为 1 或者是行数大于等于 字符串的长度,意味着只能构成一列,所以结果还是原字符串
if (r == 1 || r >= n) {
return s;
}
// t 为 周期
int t = r * 2 - 2;
// c 为 二维矩阵的列数
int c = (n + t - 1) / t * (r - 1);
// 创建一个二维字符数组 行数为 r ,列数为 c
char[][] mat = new char[r][c];
for (int i = 0, x = 0, y = 0; i < n; ++i) {
// 将字符存入二维数组
mat[x][y] = s.charAt(i);
// 如果 第i个字符的下标 取余 周期 t < 周期的列数,说明这一列已经满了,需要往右上方存储
if (i % t < r - 1) {
++x; // 向下移动
} else {
--x; // 向上移动
++y; // 向右移动
}
}
// 创建一个可变字符串进行读取
StringBuffer ans = new StringBuffer();
for (char[] row : mat) {
for (char ch : row) {
if (ch != 0) {
ans.append(ch);
}
}
}
return ans.toString();
}
运行结果:
行数为 3 的字符串‘PAYPALISHIRING’经过变形后为:PAHNAPLSIIGYIR
行数为 4 的字符串‘PAYPALISHIRING’经过变形后为:PINALSIGYAHRPI
方法二:压缩矩阵空间
可以看到上面的方法中我们有许多空字符,所以在后续读取时需要进行if(ch!=0)
判断,而且这样也浪费空间,那么还有什么办法呢?
其实我们可以初始化对应行数的可变字符串数组,当这个字符属于第 n 行,我们就将它添加到对应行数的字符串的末尾,用文字说明比较抽象,我举个栗子
A - E - I -
B D F H J L
C - G - K -
假设现在是 3 行 变换后的字符串为 A E I B D F H J L C G K
那么我初始化一个 长度为 3 的可变字符串数组
StringBuffer[] stringBuffer= new StringBuffer[3];
用下面的三条线代替
_ _ _ _
_ _ _ _
_ _ _ _
首先 A 是在第一行的,所以我们将 A 存入第一行
A _ _ _
_ _ _ _
_ _ _ _
接着是 B C
A _ _ _
B _ _ _
C _ _ _
重点来了,下一个字符是 D,它应该存在第几行呢?
根据上面的二维矩阵可以看出它是在第二行,所以我们直接存在第二行就行了
A _ _ _
B D _ _
C _ _ _
以此类推
A E I _
B D F H J L
C G K _
现在我们将它们每一行拼接起来就能得到 A E I B D F H J L C G K
与上面的答案是一样的,并且我们创建的是可变字符串,也没有将空字符存入,所以将空间压缩了
讲解完了原理,那么开始编写代码吧
public String convert(String s, int numRows) {
// n 是 该字符串的长度, r 为行数 , t 为周期
int n = s.length(), r = numRows, t = 2 * (r - 1);
// 如果行数为 1 或者是行数大于等于 字符串的长度,意味着只能构成一列,所以结果还是原字符串
if (r == 1 || r >= n) {
return s;
}
// 创建一个可变长度的字符串数组,长度为 r
StringBuffer[] mat = new StringBuffer[r];
// 初始化这个数组
for (int i = 0; i < r; ++i) {
mat[i] = new StringBuffer();
}
for (int i = 0, x = 0; i < n; ++i) {
// 将 字符 存入
mat[x].append(s.charAt(i));
// 如果 第i个字符的下标 取余 周期 t < 周期的列数,说明这一列已经满了,需要往右上方存储
if (i % t < r - 1) {
++x; // 往下一行存储
} else {
--x; // 往上一行存储
}
}
// 创建一个新的可变字符串
StringBuffer ans = new StringBuffer();
// 逐行拼接
for (StringBuffer row : mat) {
ans.append(row);
}
return ans.toString();
}
总结
以上就是Z字形变换的全部解析了,感谢大家观看,如有错误或更好的解法,可以评论或私信博主进行探讨,如果觉得内容对您有帮助,麻烦点个赞支持一下博主,谢谢!