LeetCode第6题思悟——Z 字形变换(zigzag-conversion)

第六题

题目要求

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:

L   C   I   R
E T O E S I I G
E   D   H   N

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"

The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)

P   A   H   N
A P L S I I G
Y   I   R

And then read line by line: "PAHNAPLSIIGYIR"

示例

输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"

输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
解释:
L     D     R
E   O E   I I
E C   I H   N
T     S     G

我的思路

首先,使用s=“12345678910”,numRows=3/4梳理一遍。这时就会发现:

1   5   9
2 4 6 8 10
3   7

1     7      13
2   6 8   12 14
3 5   9 11   15
4    10      16

注意到一个’N’子形中,处于相同一行的字母可能有三个也可能有两个(最上方和最下方),但是不论两个还是三个字母,'N’的特点就是上下齐宽,也就说间距是一样的,而这个间距可以分析出为2numRow-2(即2numRow-1-1)。同时这个间距被分为两部分:左边一部分和右边一部分,它们的和就是这个间距。对于同一行的数字,可以发现间距出现的规律为左、右、左、右。所以我们还需要计算出第一个间距的表达式。第i行的第一个字母,其索引为记为i,则其旁边的字母索引为(n-i)+(n-i)-1,于是左边间距为2(n-i)-2;所以第i行的间距规律为:[2(n-i)-2,2n-2-2n+2i+2=2i]。考虑到第一行和第后一行只有一个间距,即可完成任务:

public static String convert(String s, int numRows) {
    if(s==null||s.equals("")){
        return "";
    }
    if(numRows==1){
        return s;
    }
    int firstDelta,secondDelta,nextPosition,indicator;
    int stringLength=s.length();
    int deltaSum=2*numRows-2;//总间距
    char[] chars=s.toCharArray();
    StringBuilder builder=new StringBuilder(s.length());
    for(int i=0;i<numRows;i++){
        nextPosition=i;
        firstDelta=2*(numRows-i)-2;//第一个间距
        secondDelta=deltaSum-firstDelta;//第二个间距
        indicator=0;
        while(nextPosition<stringLength){//该行尚未结束
            builder.append(chars[nextPosition]);
            if(indicator==0){//如果下一个间距为左间距
                nextPosition+=firstDelta;
                if(firstDelta==0){//处理最后一行的情况
                    nextPosition+=secondDelta;
                }
                indicator=1;
            }else{//下一个间距为右间距
                nextPosition+=secondDelta;
                if(secondDelta==0){//处理第一行的情况
                    nextPosition+=firstDelta;
                }
                indicator=0;
            }
        }
    }
    return builder.toString();
}

运行结果为:在这里插入图片描述

优秀解法

//26ms
public static String convert(String s, int numRows) {
    if(numRows == 1)
        return s;
    List<StringBuilder> rows = new ArrayList<StringBuilder>();
    int len = Math.min(s.length(), numRows);
    for(int i = 0; i < len; i++){
        rows.add(new StringBuilder());
    }
    int curRow = 0;
    boolean flag = false;
    for(char c : s.toCharArray()){
        rows.get(curRow).append(c);
        if(curRow == 0 || curRow == len - 1)
            flag = !flag;
        curRow += flag ? 1 : -1;
    }
    StringBuilder sb = new StringBuilder();
    for(StringBuilder now : rows){
        sb.append(now);
    }
    return sb.toString();
}

上面的这种方法很直白,算是题意的直接翻译。按照’N’的顺序从上到下,然后在从下到上,每一行使用StringBuilder来存储,然后做汇总。没有额外的计算和判断,性能支出主要在List的访问。直接翻译也算是一种不错的解题思路,虽然不见得是最佳,但有好胜于无。在54题螺旋矩阵中,这种思路也有体现。

//12ms
public static String convert(String s, int numRows) {
    if (numRows == 1) return s;
    StringBuilder ret = new StringBuilder();
    int n = s.length();
    int cycleLen = 2 *numRows - 2;
    for (int i = 0; i < numRows; i++) {
        for (int j = 0; j + i < n; j += cycleLen) {
            ret.append(s.charAt(j + i));
            if (i != 0 && i != numRows - 1 && j + cycleLen - i < n)
                ret.append(s.charAt(j + cycleLen - i));
        }
    }
    return ret.toString();
}

这种解法算是对我自己的解法的一种升级,所依赖的规律是一样的,但是利用方式却不同。很好,很强大。

差别在哪里

不说直接翻译题意的解法,我们来看看最后一种解法。

基本思路是一样的,发现排列规律后,利用该规律,但是如何利用,确是各有不同啦。外循环都是一样的,都是对行的遍历,这也是题目要求的。但是在内循环上,却风格不同。

在我的解法中,将间距分为两部分,并通过标记变量标记下一个间距,同时对第一行和第末行做特殊处理;这个变量使用了int,很明显不好,应该用boolean。

在最后的解法中,将间距视为一部分。在读入一个字母后,如果不是首行或者尾行的话,就在加入一个字母。也就是直接加入相隔左间距的那个字母。

这样一来,就内循环的次数来说,肯定是最后解法更少,因为单步跨度大。就判断次数来说,我的解法中需要不断判断下一个间距的类别,判断次数也比后者多。这应该就是时间差了。

在我的解法中,只是注意到了两种间距的差异性,按照左、右、左、右的规律完成代码;而最后一种解法却是看到了它们的一致性,(左、右)、(左、右)。代码不但更加简洁、优美,效率也高,而这两者的区别仅在于看待问题的方法和角度不同。值得学习。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值