Java算法题:Z字形变换

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字形变换的全部解析了,感谢大家观看,如有错误或更好的解法,可以评论或私信博主进行探讨,如果觉得内容对您有帮助,麻烦点个赞支持一下博主,谢谢!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值