LeetCode6-Z 字形变换
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入: s = “LEETCODEISHIRING”, numRows = 3
输出: “LCIRETOESIIGEDHN”
示例 2:
输入: s = “LEETCODEISHIRING”, numRows = 4
输出: “LDREOEIIECIHNTSG”
解释:
L D R
E O E I I
E C I H N
T S G
一、思路
(一)直觉想法
题目给出字符串和每行字符个数,整个Z字形变换就已经完成了,同一行的字符在字符串中的间隔应该是一样的(除了最后的字符)
1、例子
输入: s = “LEETCODEISHIRING”, numRows = 3
第一个字符s[0]下面还有2个字符(s[1],s[2]),再加上Z字形中间的1(s[3])个,下一个字符应该是s[4]
2、推广
输入:s,numRows=m,假定字符串长度为n
那么整个Z字形组成的矩阵应有:
m行k列,将含有m个字符的列称为满列,设满列有k1个,令k2=k-k1
两个满列之间间隔:m-2列,且满列之间的列,每列只有1个字符,且:n = k1*m+k2
-
第一(m)行的字符:每个字符在字符串中的间隔是:(m-1)+(m-2)+1
简化为: 2 ( m − 1 ) 2(m-1) 2(m−1) -
第二行的字符:嗯,遇到问题了,不同的字符,间隔可能不一样的
实际上呢,这是由于上下不对称造成的,需要分情况讨论,不过也不复杂,就只有两类:
(1)第奇数次寻找下一个字符时,间隔:(m-2)+(m-3)+1,简化为: 2 ( m − 2 ) 2(m-2) 2(m−2)
(2)第偶数次寻找下一个字符时,间隔:2 -
第k(k不等于1或者m)行的字符:同样要分情况讨论:
(1)第奇数次寻找下一个字符时,间隔:(m-k)+(m-k-1)+1,简化为: 2 ( m − k ) 2(m-k) 2(m−k)
(2)第偶数次寻找下一个字符时,直接想不好想出来,反过来想,这个字符位于两个满列之间,行数是第k行,如果倒转过来,将第m行视为第一行,那么第k行则被视为第m-k+1行,那么间隔为:(m-(m-k+1))+(k-2)+1,这里的(k-2)是因为第一个数在第一项已经计算过了,不再重复记录,简化为: 2 ( k − 1 ) 2(k-1) 2(k−1)
这么一来所有的情况都考虑到了
C++代码如下:
class Solution {
public:
string convert(string s, int numRows) {
string s_z;
int high = s.size() - 1, k = 0;
int len = high + 1;
if (len == 1 || numRows == 1) {
return s;
}
s_z.reserve(len);
// 获取第一行字符
for (int i = 0; i <= high; ) {
s_z[k++] = s[i];
i = i + 2 * (numRows - 1);
}
// i表示行数,i1的初值表示,第i行的第一个字符
for (int i = 2; i < numRows; i++) {
int i1 = i - 1;
for (int j = 1; i1 <= high; j++) {
if (j % 2 != 0) {
s_z[k++] = s[i1];
i1 = i1 + 2 * (numRows - i);
}
else {
s_z[k++] = s[i1];
i1 = i1 + 2 * (i - 1);
}
}
}
// 获取最后一行的字符
for (int i = numRows - 1; i <= high; ) {
s_z[k++] = s[i];
i = i + 2 * (numRows - 1);
}
s_z[k] = '\0';
return s_z;
}
};
执行效率: