力扣:Z字形变换 难度:中等

目录

1、本题的地址和题目

2、分析一下题目

3、我的解法和代码及执行结果

4、优化算法和解析

5、总结


1、本题的地址和题目

本题是在力扣上面做的,难度是中等级别,地址如下:

6. Z 字形变换 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/zigzag-conversion/

将一个给定字符串 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);

实例和数据范围如下:

2、分析一下题目

这一道题的题目是很容易理解的,就是按照Z字形将给的字符串重排,给定的有排列的行数。重排之后,再按照从左到右,从上到下的顺序正常组成一个字符串返回即可。字符串的长度也不大,最多一千个字符,其中不仅有英文字母,还有英文逗号和句号 。

3、我的解法和代码及执行结果

这一道题看似很简单,其实它考验的就是算法(使用一个高效的方法解决问题)。我没有想到比较高效的解法,因此再次使用了暴力破解,哈哈哈哈,证明我真的很菜。

最容易想到的就是使用char类型的字符数组存储重排后的字符。已经给出了字符串和要排列的行数,可以计算出需要使用的二维数组的大小(这个二维数组的大小直接影响执行的效率)。但是我嫌弃算起来很麻烦,脑子不够用,所以我就没有很用心的计算,直接使用了s.length()作为二维数组的横纵坐标。有了二维数组以后,还需要对每一个字符应该处的位置确定一下,我使用了两个int型的变量存储。由于是Z字形,那就有向下走的,也有向上走的。可以很容易的发现,当向下走的时候,只有行标在变,列是不变的。当向上走的时候,行标和列标都是在变的。那么我们就需要判断某一刻字符排列是正在向下走还是正在向上走,因为不同的走向意味着行标和列标的变化情况不同。我们还要注意什么时候需要转向了。

这里我使用了一个boolean类型的变量标记正在向上走还是向下走,使用row和column两个整型变量记录下一个字母应该放在那个坐标位置。

我先使用0这个字符来初始化我的二位数组,然后使用一个for循环对字符串中的所有字符进行遍历。在遍历的时候,先将这个字符放在对应的row和column描述的位置上。然后根据boolean标记的向上或者向下,对row和column做出调整。调整之后,还要判断是不是越界了(如果越界了,就证明这一次是应该要转向了),一旦越界了,那么就要更改boolean变量的标记,然后撤回刚才的调整,并且按照新更改的标记对应的调整规则对row和column进行调整。最后再使用一个双重循环,对二位数组进行判断,如果一个位置上不是0字符,就将这个字符加在StringBuffer(线程安全的,长度可变)的后面。然后最后将StringBuffer变量通过String的valueof()静态方法转变为一个String变量返回。

特别注意:这里有一个特殊情况,假如给的numRows是1,也就是二维数组只有一行(其实就是一维数组),那么这种情况下直接返回字符串就可以了(使用我上面的方法反而会出错),需要提前判断一下。其实这里还可以优化:假如字符串的长度小于numRows,那么就意味着第一列都没有排满,返回的时候从左到右,从上到下,其实还是原先的字符串,只不过这种情况使用我上面的解法是可以的。(这种情况我没有想到)。本来第一种情况我都没有想到,太笨了,是我提交以后不通过,系统提示了这个错误示例!!

多说一下:这一点力扣好像比牛客人性化一点,它还告诉你那个测试用例没有通过,牛客直接给的是样例通过率,让你想不到自己的解法哪里有漏洞,不人性。不过力扣是遇到一个样例不通过,后面的样例不再检测,直接停止检测,牛客会将所有的样例都检测一遍。

下面是我的代码:

 public static String convert(String s, int numRows) {

        char a[][]=new char[s.length()][s.length()];
        for (int m=0;m<s.length();m++){
            for (int n=0;n<s.length();n++){
                a[m][n]='0';
            }
        }
        boolean flag=true;//true表示往下,false表示往上
        int row=0;//记录目前的字符要放在的行
        int column=0;//记录目前的字符要放在的列
        if (numRows==1){//这个是需要单列出来的,否则下面row=row-2的时候,row会变成-1,
            return s;
        }else {
            for (int i=0;i<s.length();i++){
                a[row][column]=s.charAt(i);
                if (flag==true){
                    row=row+1;
                    if (row==numRows){//证明该转向了,下面就是撤回调整,并且整合else下面的调整
                        flag=false;
                        row=row-2;
                        column=column+1;
                    }
                }else {
                    row=row-1;
                    column=column+1;
                    if (row==-1){//证明该转向了,下面就是撤回调整,并且整合上面 if下面的调整
                        flag=true;
                        row=row+2;
                        column=column-1;
                    }
                }
            }
            StringBuffer buffer=new StringBuffer("");
            for (int m=0;m<s.length();m++){
                for (int n=0;n<s.length();n++){
                    if (a[m][n]!='0'){
                        buffer=buffer.append(a[m][n]);
                    }
                }
            }
            return String.valueOf(buffer);
        }

    }

执行结果如下图:

 但是无论是执行用时还是内存消耗,我这个解法都是排在最末尾的,非常不推荐。如果是在ACM中的话,那么应该会引发超时错误的。(我的代码里面,如果再将二维数组设置的大一点,都设置为1000的话,那么就会超时错误)

我的解法的空间复杂度是O(n^2),也就是我使用了一个二维数组,这里的n是字符串的长度。时间复杂度可以说是O(n)吧,因为主要的代码是对字符串进行遍历。不过我前后还对二维数组进行了遍历。

4、优化算法和解析

第一种解法我们可以很精确的根据字符串的长度和要求的行数计算出每一个字符串中的字符在数组中对应的下标,这个是有规律的,但是我是真的不想找这个规律,脑子不够用,哈哈哈。这种解法我没有去想,官网也有,喜欢的可以去看。

第二种解法就是采用多个一维数组的方法。我们可以换一种想法,虽然是进行Z字形排列,但是每个字符相比于上一个字符而言,所在的行肯定是发生变化了。就是从0行到numRows-1行,然后再到0行。就这样变换。最后输出的时候,按照正常的顺序从左到右,从上到下。其实正常的顺序就是依次输出每一行的字符。那么我们可以直接使用numRwos个一维数组,然后对字符串进行遍历,每一个字符就放在下一个一维数组的后面就行啦。最后依次将这numRows个数组加在一个字符串后面,返回这个字符串就行啦。或者说,不使用一维数组,就使用numRows个StringBuffer对象就可以。这里需要注意的就是Z字型变换,什么时候要转变方向了

第二种解法的代码如下:

public static String convert(String s, int numRows) {
            StringBuffer buffer[]=new StringBuffer[numRows];
            for (int i=0;i<numRows;i++){
                buffer[i]=new StringBuffer("");
            }
            if (numRows==1||numRows>=s.length()){
                return s;
            }else {
                boolean b=true;//true表示向下走,false表示向上走
                int j=0;//记录目前的字符要放在哪一行
                for (int i=0;i<s.length();i++){
                    buffer[j].append(s.charAt(i));
                    if (i%(2*numRows-2)==(numRows-1)){//说明要转向了
                        b=false;
                        j--;
                    }else if (i%(2*numRows-2)==0){//说明要转向了
                        b=true;
                        j++;
                    }else if (b==true){
                        j++;
                    }else {
                        j--;
                    }
                }
                StringBuffer stringBuffer=new StringBuffer("");
                for (int i=0;i<numRows;i++){
                    stringBuffer.append(buffer[i]);
                }
                return String.valueOf(stringBuffer);
            }

        }

上面的代码中,我首先创建了一个StringBuffer数组,下面分别对它们进行初始化,也就是需要new一下,否则是会出现空指针异常的。然后就还是要注意只有一行的情况,要单独列出来,否则走下面的就会出错。然后就是设置了一个标志位和变量j(记录下一个字符要放在哪一行)。至于那个判断条件,这个需要简单的在纸上画一下,比如说给的numRows是5,那么行坐标变换的一定就是0,1,2,3,4,3,2,1,0,1,2,3,4,.....,这明显是有规律的,0,1,2,3,4,3,2,1就是一个规律组,当j等于0的时候,要转向了,向下走;当j等于4(numRows-1)的时候,还要转向,向上走。转向就意味着j改变标志位,j加一或者减一。当然,当不是要转向的时候,就根据布尔类型的标志位判断j需要加一或者减一。遍历到那个字符的时候该转向了,这个当然与i和numRows都有关,这个就要自己找规律了。其实就是当字符填入0行或者是numRows-1行的时候,就需要转向了

下面是这种解法代码的执行结果:

 

 

明显这一次的解法比第一次好了很多。

这种解法的空间复杂度是O(n),n是字符串的长度。明显的使用了2n的空间,因为先用了StringBuffer数组存储,然后再使用一个StringBuffer拼接它们(其实可以直接使用第一个StringBuffer拼接就行啦) 。时间复杂度也是O(n),也就是对字符串进行了遍历。

5、总结

其实这道题最常见的也就是上面的三种解法。第二种其实更好一点,直接算出来每个字符应该在那个位置,应该会更快一点,只不过需要前期自己认真的找规律。最后一种方法,官网上也有很多人在用,但是同样的解法,不同的人编写的代码,运行时间和内存消耗量还是存在比较大的差距。这就牵扯到对程序运行的具体情况的了解和语言的掌握情况了。

比如某一个地方可以不使用判断,某一个地方使用其他的数据结构会跟快,某个地方可以使用短路判断。这些虽然看似不起眼,但是一旦数据量大了,差距也就显现出来了。

今后的学习,不仅要注重解法,也要注重的是对于语言的掌握,指导代码背后的运行情况

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你是我的日月星河

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值