题目:将一个给定字符串 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);
示例输入:
示例1:
输入:s = "PAYPALISHIRING", numRows = 3
输出:"PAHNAPLSIIGYIR"
示例2:
输入:s = "PAYPALISHIRING", numRows = 4
输出:"PINALSIGYAHRPI"
解释:
P I N
A L S I G
Y A H R
P I
示例3:
输入:s = "A", numRows = 1
输出:"A"
提示:
-
1 <= s.length <= 1000
-
s
由英文字母(小写和大写)、','
和'.'
组成 -
1 <= numRows <= 1000
题解:
需要寻找一些规律,简单来说就是字符串中的每一个下标都可以通过某一种方式来找到他们对应的行。
获得对应行的方法就是使用下标对2*numrows-2
取模取模之后得到的结果范围在0~2*numrows-1
之间,而行号范围在0~numrows-1
之间,因此还需要再进行一次处理,对于在numrows~2*numrow-1
之间的结果,使用2*numrows-2
减去他们,得到正确的行数。例如,numrows=4,对于下标为11的字符,取模得到
11
%
(
2
×
4
−
2
)
=
5
11\%(2\times 4-2)=5
11%(2×4−2)=5,
(
2
×
4
−
2
)
−
5
=
1
(2\times 4-2 )-5=1
(2×4−2)−5=1,因此在第一行。
这是一个对应方法,得到这个对应方法后可以建立一个哈希表,把同一行的字符按照顺序存进去,最后通过遍历这张表得到正确的输出顺序。
当然,还有另一种更好的解法。
我们已经知道2*numrows-2
是同一行之间的两数间隔,对于第一行和最后一行来说只有这一种间隔,而对于中间行来说,在这样一个间隔中间,每个数在边界范围内通过下标加一个常数得到的数也在这一行,但不能通过简单的取模得到,例如:
P0 I6 N12
A1 L5 S7 I11 G13
Y2 A4 H8 R10
P3 I9
填上了每个字符的下标,第一行每个数字间隔就是6;第二行1,7,13间隔为6,而A(1)L(5);S(7)I(11)间隔为4…说明这其中也有一些数值联系。
我们首先通过行循环,对于第一行,我只要从0开始+6就能得到需要的字符;第二行开始,我们需要判段附加字符,这个间隔其实相当于当前字符在这一行的序列数+2*numrows-2
-行数,通过这样的方式可以顺序地得到字符。具体流程见代码。
代码:
算法1:哈希
class Solution {
public:
string convert(string s, int numRows) {
if(numRows<2)
{
return s;
}
map<int,vector<int>> hash;
int mod=2*numRows-2;
for(int i=0;i<s.size();i++)//遍历存储对应行数
{
int tmp=i%mod;
if(tmp<numRows)
{
hash[tmp].push_back(i);
}
else
{
hash[mod-tmp].push_back(i);
}
}
string res="";
for(auto i:hash)//构造输出字符串
{
for(auto j:i.second)
{
res+=s[j];
}
}
return res;
}
};
算法2
class Solution {
public:
string convert(string s, int numRows) {
if(numRows<2||numRows>=s.size())
{
return s;
}
string res="";
int to_mod=2*numRows-2;
for(int r=0;r<numRows;r++)//行数遍历
{
for(int i=0;r+i<s.size();i+=to_mod)//字符序列遍历,增量为2*numrows-2
{
res+=s[r+i];
if(r!=0&&r!=numRows-1&&i+to_mod-r<s.size())//加入附加字符
{
res+=s[i+to_mod-r];
}
}
}
return res;
}
};