题目:Z 字形变换
将一个给定字符串 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);
示例:
输入:s = "PAYPALISHIRING", numRows = 4
输出:"PINALSIGYAHRPI"
解释:
P I N
A L S I G
Y A H R
P I
方法一:自己通过每个数规律找到的方法
每一行看作一个集合,最后按顺序输出
先解释一下题意
输入字符串后像Z(蛇形)一样排列 排列后的字符串从第一行,一行一行输出。
说下我自己的思路:
为了方便说明,画出了每个字母的下标
发现的一些规律:
如图:每6个数字一组循环,竖线+斜线。我们以第一组为例
竖线上有4个元素,下标为0到3。定义四个元素集合,对应字符分别放入第0个元素集合,第1个元素集合、第2个元素集合、第3个元素集合里。
斜线上有2个元素 ,下标为4和5。放入第3个元素集合和第2个元素集合里。
推广:
i 表示对应字符的下标
竖线上有numRows个元素,斜线上有 numRows-2 个元素, numRows + numRows - 2个元素(称为 j )一组循环,竖线+斜线的元素和。
竖线上的元素放到第 i % j 个元素集合,斜线上放入第 j - (i % j) 个元素集合里。最后按集合顺序读取元素即可。
最开始我不知道该用什么集合存储,用过四个数组,还用过map集合。看了官方解答用 list 集合存储四个 StringBuilder 感觉比我想到的存储办法都好。
//每个竖线+斜线为一组 自己的想法
public static String convert(String s, int numRows) {
//Map<Integer, String> str = new HashMap<>();
//String[] str = new String[numRows];
if (numRows == 1) {
return s;
}
//竖线有几个元素就创建几个StringBuilder,
List<StringBuilder> rows = new ArrayList<>();
//Math.min(numRows, s.length()),防止字符个数比传入的numRows小,创建多余的StringBuilder浪费空间
for (int i = 0; i < Math.min(numRows, s.length()); i++) {
rows.add(new StringBuilder());
}
int n = s.length();
//j个元素为一组循环
int j = numRows + numRows - 2;
for (int i = 0; i < n; i++) {
//i再竖线上
if (i % j < numRows) {
//把对应字符存入第i % j个元素集合
rows.get(i % j).append(s.charAt(i));
} else {
//i在斜线上,把对应字符存入第j - (i % j)个元素集合
rows.get(j - (i % j)).append(s.charAt(i));
}
}
//输出字符串
StringBuilder ret = new StringBuilder();
for (StringBuilder row : rows) {
ret.append(row);
}
return ret.toString();
}
看了官方的解答,感觉点像按行排序,但是比官方的方法消耗内存多点。
方法二:按行排序
相比方法一,去掉了几个元素为一组,增加个flag变量。当flag = true则表明 i 在竖线上。当flag = false则表明 i 在斜线上。每次 i 到最底下或最顶上一个元素时 flag 改变。
//每次到最下面一行就—1 最上一行就+1 存储
public static String convert(String s, int numRows) {
if (numRows == 1) return s;
List<StringBuilder> rows = new ArrayList<>();
for (int i = 0; i < Math.min(numRows, s.length()); i++)
rows.add(new StringBuilder());
int curRow = 0;
boolean flag = false;
for (char c : s.toCharArray()) {
rows.get(curRow).append(c);
if (curRow == 0 || curRow == numRows - 1) flag = !flag;
curRow += flag ? 1 : -1;
}
StringBuilder ret = new StringBuilder();
for (StringBuilder row : rows) ret.append(row);
return ret.toString();
}
方法三:按行访问
由方法一的规律,我们可以知道每个元素的下标。把第0行元素都存储到StringBuilder中,再把第一行元素都存储StringBuilder中,直到第numRows-1行。
//官方二
public static String convert(String s, int numRows) {
if (numRows == 1) return s;
StringBuilder ret = new StringBuilder();
int n =s.length();
//cycleLen个元素为一组
int cycleLen = numRows+ numRows-2;
for (int i = 0;i<numRows;i++){
for (int j =0;j+i<n;j+=cycleLen ){
//i位于竖线上
ret.append(s.charAt(j+i));
//i不在第0行也不在最后一行(即i在斜线上)
if ( i != 0 && i != numRows-1 && j+cycleLen-i<n){
ret.append(s.charAt(j+cycleLen-i));
}
}
}
return ret.toString();
}
这种方法是最优解了。
总结:
这题相对来说不算太难,只要想到规律就很容易解决。
for (int i = 0; i < s.length(); i++) {
rows.add(new StringBuilder());
}
这么创建StringBulider头次见过,要记住。
同时map的value还可以存储list类型。也比较常用