博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
LeetCode (6): ZigZag Conversion
阅读量:6852 次
发布时间:2019-06-26

本文共 5261 字,大约阅读时间需要 17 分钟。

 【目】LeetCode(6:  ZigZag Conversion

URL:   https://leetcode.com/problems/zigzag-conversion/

【描述】

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   NA P L S I I GY   I   R

And then read line by line: "PAHNAPLSIIGYIR"

Write the code that will take a string and make this conversion given a number of rows:

string convert(string text, int nRows);

convert("PAYPALISHIRING", 3) should return "PAHNAPLSIIGYIR".

【中文描述】给一个String比如"PAYPALISHIRING",要求返回zigzag写法后,一行一行输出的结果。比如上面结果就是从第一行PAHN,到第二行APLSIIG,到第三行YIR结束。所以最后的输出是:“PAHNAPLSIIGYIR”。  

————————————————————————————————————————————————————————————

【初始思路】

刚开始看了10分钟没看懂题目,汗~~~

后来自己写了个,一提交错了,看了下测试用例,才明白所以然。

其实所谓zigzag意思是,你的输入字符串,从上往下先竖写一列,然后从下往右上写一斜线,然后再竖着写下来,再往右上,如此反复,如下图所示。

                                              

然后,就brute写了一个,思想是直接硬按照规律找位置从string里挑字符出来拼。

过程很郁闷,因为首尾两行情况特殊考虑,中间行又要找规律,找了好久都没找出个所以然来。最后正面刚写了个程序出来,很难读懂。。:

1 public String convert(String s, int numRows) { 2         if(numRows == 1 || numRows >= s.length()) { 3             return s; 4         } 5         StringBuilder sb = new StringBuilder(); 6         int length = s.length(); 7         int r = length % (2 * numRows - 2); //  total remain number, indicate that how many rows need to add more element 8         int c = length / (2 * numRows - 2); //  columns number 9         int excol = 0;10         int remain = 0; // total remain - numRow = remain11         if(r >= numRows) {12             excol = 1;13             remain = r - numRows;14         } else {15             excol = 0;16             remain = r;17         }18 19         for(int i = 0; i < numRows; i++) {20             if (i == 0) {21                 for(int j = 0; j < c + (r > 0 ? 1:0); j++) {22                     sb.append(s.charAt((2 * numRows - 2) * j + i));23                 }24             } else if(i == numRows - 1){25                 for(int j = 0; j < c + excol; j++) {26                     sb.append(s.charAt((2 * numRows - 2) * j + i));27                 }28             } else {29                 int j = 1;30                 sb.append(s.charAt(i));31                 while(j * (2 * numRows - 2) - i < length) {32                     sb.append(s.charAt(j * (2 * numRows - 2) - i));33                     if(j * (2 * numRows - 2) + i < length) {34                         sb.append(s.charAt(j * (2 * numRows - 2) + i));35                     }36                     j++;37                 }38             }39         }40         return sb.toString();41     }
convert

 

 

【反思】

然后看了网上大神的思路(汗,easy题都搞不出来。。。),豁然开朗,考虑如下图(原谅我,懒得用photoshop画图了):

                                                       

显然,第一眼就能发现的规律是,有一个循环cycle存在(图中以虚线隔开),在numRow=3的时候,这个cycle = 4。如上图,整个string被分成了4个cycle(最后一个不满,待会儿讨论)。

那么很显然,从任一个cycle 任一个字符到下一个cycle相同位置的字符,需跳跃cycle个位置。比如,第一个字符P,到第一行第二个字符A,需跳跃cycle个字符。所以A的位置为 0 + cycle = 4。

到这里,首尾两行已经基本分析完了。算出cycle数,以 i 为行号,然后在每一行内以 k 循环,依次输出 k + cycle位置的字符(如果此位置小于String长度)即可。

 

这个题实际的难点在于z字中间行的规律寻找。找到迎刃而解,找不到就慢慢正面刚代码吧。这是这个题easy的原因,也是我认为它很无聊的原因。。。

还是看上面图,中间行为"APLSIIG",这一行的规律是什么呢?

我们可以看到,这一行每个cycle其实有2个字符(除了最后未满的cycle):AP/LS/II。 这是一个很重要的发现:

首先,无论numRows为多少,这个规律是不变的;

其次,从这2个字符中第一个字符跳到下一个2个字符的第一个字符,跳跃数也是cycle。比如,图上A到L,其实就是 A的位置+cycle = L的位置。

好,如果我们把这一行也纳入到上面首尾两行的循环中去,可以轻松找到所有2字符组的第一个字符,我们称2字符组第一个字符位置为j,所处行行号为 i, 有如下规律:

         j  = i + cycle * k,   k = 0, 1, 2, 3, ......

同时可以看到,对于中间行,在循环中对每个 k, 要找2个字符。找到j的同时,再找到第二个字符的位置 second_j,一直循环到结束,这一行不就输出了么?

那么second_j有何规律?

For numRows ==3的情况, APLSIIG的规律看似很简单,因为他们的位置是:1、3、5、7、9、11、13。那么second_j = j + 2?,我们来验证一下。

假设numRows==5,那么题目string的zigzag结果应该是:

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

比如,对于i=1(第二行)来看,第一个cycle的j=1,second_j = 7。对于i=2, 第一个cycle的j=2, 而second_j = 6。显然上面的结论是错误的。

我们可以观察到,i越大,它的 second_j 和 j 之间的差距越小。  所以,可以假设second_j = N - c * i。N为某数字, c应当为 i 的系数。同时显然可以猜测,N应当是j的线性函数。那我们假设N = a * j + b。

所以,得到式子: second_j = a * j + b - c * i。  接下来,我们试试解这个方程组。

根据上面的位置数字(1-7, 2-6, 3,5),分别代入second_j、j、i,得到以下方程组:

a * 1 + b - 1*c = 7

a * 2 + b - 2*c = 6

a * 3 + b - 3*c = 5

上面三式可轻松求出,b = 8, 并且 a-c = -1. 继续,根据上面数字(3-5, 11-13) 解如下方程组:

11*a + 8 - 3c = 13

3*a  + 8 - 3c = 5

解得 a = 1, c = 2

由此,得到second_j = j + 8 - 2 * i 。

回到numRows = 3的情况,second_j = j + 8 - 2*i,我们来验证一下:3 = 1 + 8 - 2*1。 这明显不对,哪里出了问题?

思考一下,a是j的参数,c是i的参数,参数必然不会随着numRows的变化而变化,他们肯定是固定的。唯一可变的只有b。那么在numRow=5的时候,b=8, 在numRows=3的时候, b=4,上面式子才能成立。

到这里,我们发现了b的规律, b = cycle!!!

所以, second_j = j + cycle - 2* i。   代入上面各种情况验证,全部验证成功!

这样,我们就得到了second_j 和 j 的关系,那么对于每一行 i,以 k 为循环变量,每一个cycle第一个字符位置为j, 第二个位置就为 second_j = j * cycle - 2 * i。

代码呼之即出!

 

【Show me the Code!!!】

1 if(numRows <= 1) return s; 2         StringBuilder sb = new StringBuilder(); 3         //the size of a cycle(period) 4         int cycle = 2 * numRows - 2; 5         for(int i = 0; i < numRows; ++i) 6         { 7             for(int j = i; j < s.length(); j = j + cycle){ //j = j + cycle is wonderful!!! 8                 sb.append(s.charAt(j)); 9                 int secondJ = (j - i) + cycle - i;10                 if(i != 0 && i != numRows-1 && secondJ < s.length())11                     sb.append(s.charAt(secondJ));12             }13         }14         return sb.toString();
convert

此外,应当看到,其实,首尾两行可以纳入通盘考虑。当i = 0 或者 i = numRows的时候,不予考虑second_j,因为他们也没有。所以,inner-for循环始终保持跳跃 j + cycle

此外,second_j必须检查是否会越界。

最后,我想说的是,这种题好无聊~~~

 

 

转载于:https://www.cnblogs.com/lupx/p/leetcode-6.html

你可能感兴趣的文章
[原创]牛刀小试-重构并实现邮件内容生成功能
查看>>
过滤器
查看>>
Android 控件使用
查看>>
linux关闭防火墙
查看>>
贪吃蛇源码
查看>>
杂谈篇:阅读优秀代码是提高开发人员修为的一种捷径
查看>>
jfreechart 实例
查看>>
gstreamer 10.5版本发布啦
查看>>
Android--控件属性汇总
查看>>
LeetCode算法题-Implement Queue Using Stacks(Java实现)
查看>>
spring 事务
查看>>
[转自知乎] 如何成为一个优秀的程序员,而不是一个优秀的码农?
查看>>
java abstract class和interface有什么区别
查看>>
虚拟机桥接模式不能上网
查看>>
lodash.js 学习记录
查看>>
1.4 Genymotion模拟器安装
查看>>
简说设计模式——外观模式
查看>>
简说设计模式——模板方法模式
查看>>
php 图片写字
查看>>
linux之权限
查看>>