LeetCode (6): ZigZag Conversion

 【目】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   N
A P L S I I G
Y   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 = remain
11         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必须检查是否会越界。

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

 

 

posted @ 2015-09-14 03:59  RockIt  阅读(597)  评论(0编辑  收藏  举报