Z 字形变换-java

题目描述(力扣题库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

法一:
  1. 创建一个长度为 numRows 的 StringBuilder 数组 sb每个元素代表 Z 字形中的一行
  2. 遍历输入字符串中的每个字符,根据 Z 字形规则将字符添加到相应的行中。
    • 以一个指针 x 来表示当前字符应该被添加到哪一行,初始时 x = 0。
    • 通过维护一个变量 timeSum = 2 * numRows - 2,可以确定字符在 Z 字形中的行数变化规律。 
      • ​​​​​​ timeSum为一个周期内(仔细观察 z 字形排序, 不难发现它是有规律可循的),元素的个数
      • 观察上图, 不难发现每个周期有四个元素( numRows + (numRows - 2 ) )
    • 继续仔细观察上图, 不难发现, 当 i % timeSum < numRows - 1 时,x 递增;否则 x 递减。
  3. 将所有行的 StringBuilder 按顺序连接起来,形成最终的结果字符串。
  4. 返回最终结果字符串。

整体算法的时间复杂度为 O(n),其中 n 是输入字符串的长度。

具体步骤如下:
  1. 初始化检查
  •         首先检查 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();
          }
      }
      

以上就是本篇文章的全部内容,该博客文字讲述偏多,掺杂了博主自己的想法与见解,内容如若有不妥之处,敬请谅解.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值