6. 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" .
class Solution {
public:
string convert(string s, int numRows) {
}
};
解题思路:
-
自己的解题思路
一开始认为题目很简单,但是实际操作起来,发现自己的思路实现起来并没有那么简单。似乎这种感觉经常会出现。所以,不能眼高手低,一定要实现出来,绝不能想当然。
思路:
首先,将zigzag图案的每行的列数算出来;如下,
P A H N ………………………………………… col[0]=4
A P L S I I G ………………………………………… col[1]=7
Y I R ………………………………………… col[2]=3
然后计算结果res;依次从每行开始遍历,然后对于每行的每一列进行遍历,运用相应的行列对应公式就可以得出结果。
总评:对应公式比较复杂,期间调试很久。这真是一个糟糕的算法。
-
别人的解题思路
[1] 第一个方法很巧妙,它将每一个ZigZag图案拉长,变成锯齿形图案。
P A H N
A P L S I I G
Y I R
利用辅助数组string[nRows],记录每行的string,然后链接就可以得到结果。
好处,不需要复杂的源字符串与目的字符串的对应公式。
[2] 第二个方法,跟我的思路比较靠近,但是它的思路更清晰。
首先对应一个cycle
=2*nRows – 2;
这相当于步长,但是出去首行跟末行,其他行还有中间一个元素需要对应,因此在循环中还需要一个判断条件。
学习收获:
-
对 边界条件 (Boundary Conditions)加深理解
自己写的程序,一开始没有考虑边界条件。总是,直接提交,然后再看通不过的测试案例,然后再修改。虽然最后也能通过,但是在CCF认证考试,浙大的PAT考试,找工作的机试都是只能提交代码的。偶尔的系统还会告诉你,这个程序有没有全部通过测试案例,但是几乎全不会像LeetCode这么人性化还给你错误的案例。因此,必须对此加以重视,否则要找不到工作的节奏。
总结一下:这次犯的错误。
边界条件,主要就是对输入的把控。
-
如果程序中,有除法运算,一定要记得考虑是否除数为0的情况。
-
对于string,考虑size()为0的情况。对于int,考虑为0 以及正负数的情况。
本题,没有考虑到负数的情况,说明我的程序还是有漏洞的,需要重视。别人的程序就有考虑。
-
对于string部分的初始化、size以及capacity理解更深。
string a(10); //no exist such initialization but vector<int> a(10) is okay
string a(10,’\0’) //but you can do like this
对于一个string a;需要时刻考虑他的size以及capacity。
eg: string a;
a.reserve(10); //
PS:
reserve()
的效果是只增不减
a[6]
=’x’; //
就是会报错的,reserve只是改变了capacity,而不是size
对于string的其他相关知识,可以参考附件2。
-
Word技巧:如何左右观看同一个文档,以及上下观看同一个文档。
【左右】
点视图->新建窗口。 之后,会出现同一个Word文档的窗口。特点,一个窗口改变,一个窗口的Word也会同步改变。
之后,再点->并排查看 默认的情况是:同步滚动的,意思就是向下拉一个窗口,另一个窗口也在往下拉。而一般是不需要这个并排查看的,再点击一下,就可以去除。
之后,就是可以完美左右查看文档。
关闭方法:直接关闭一个窗口就可以。
【上下】点视图->拆分;就可以上下观看同一个文档的两个不同部分。
关闭方法,再次点击拆分,即可。
附件:程序
1、自己的程序:
string convert(string s, int numRows)
{
//boundary conditions: (1) s.size()==0; (2) numRows == 1
//what about other impossible instances eg:numRows == 0
if(s.size() == 0)
{
return string();
}
else if(numRows == 1)
{
return string(s.begin(), s.end());
}
//initialize res, but we cannot do like this: string res(sz);
int sz = s.size();
string res(sz, '\0');
//col[] is to store every row's length
vector<int> col(numRows, 0);
//compute col[0]
col[0] = (sz - 1) / ((numRows << 1) - 2) + 1;
//compute col[ 1, 2, ..., numRows-2 ]
for(int i = 1; i < numRows - 1; ++i)
{
if(sz - ((numRows - 1)*((col[0] - 1) << 1) + 1 + i) < 0)
{
col[i] = (col[0] - 1) << 1;
}
else if(sz - ((numRows - 1)*((col[0] - 1) << 1) +
(numRows << 1) - i - 1) < 0)
{
col[i] = (col[0] << 1) - 1;
}
else
{
col[i] = col[0] << 1;
}
}
//compute col[numRows-1]
if(sz - ((numRows - 1)*((col[0] - 1) << 1) + numRows) < 0)
{
col[numRows - 1] = col[0] - 1;
}
else
{
col[numRows - 1] = col[0];
}
//to get the res
for(int index = 0, i = 0; i < numRows&&index != sz; ++i)
{
if(i == 0 || i == numRows - 1)
{
for(int j = 0; j != col[i]; ++j)
{
res[index++] = s[(((numRows - 1)*j) << 1) + i];
}
}
else
{
for(int j = 0; j != col[i]; ++j)
{
if(j % 2 == 0)
{
res[index++] = s[(j >> 1)*((numRows - 1) << 1) + i];
}
else
{
res[index++] =
s[(j >> 1)*((numRows - 1) << 1) + (numRows << 1) - i - 2];
}
}
}
}
return res;
}
2、别人的程序
string convert(string s, int nRows)
{
if(nRows <= 1)
return s;
const int len = (int)s.length();
string *str = new string[nRows];
int temp = (len - 1) / ((nRows << 1) - 2) + 1;
int limits = temp << 1;
for(int i = 0; i < nRows; ++i)
{
str[i].reserve(limits);
}
int row = 0, step = 1;
for(int i = 0; i < len; ++i)
{
str[row].push_back(s[i]);
if(row == 0)
step = 1;
else if(row == nRows - 1)
step = -1;
row += step;
}
s.clear();
for(int j = 0; j < nRows; ++j)
{
s.append(str[j]);
}
delete[] str;
return s;
}
string convert(string s, int nRows)
{
if(nRows <= 1) return s;
string result = "";
//the size of a cycle(period)
int cycle = 2 * nRows - 2;
for(int i = 0; i < nRows; ++i)
{
for(int j = i; j < s.length(); j = j + cycle)
{
result = result + s[j];
//j-i 回跳到之前尖点[行号为0], +cycle 则是跳到下一个尖点
//-i 则是回跳到前面的第i行 思路很清晰
int secondJ = (j - i) + cycle - i;
if(i != 0 && i != nRows - 1 && secondJ < s.length())
result = result + s[secondJ];
}
}
return result;
}
附件2:扩展阅读(转载)
- C++中string的size,length,capacity三者到底有何区别求解. https://zhidao.baidu.com/question/1048376565616057099.html.
测试发现
1. std::string value(2, ‘a’);
结果: value.size() == value.length()==2; value.capacity()==31
std::string value(31, ‘a’);
结果: value.size() == value.length()==value.capacity()==31;
2. std::string v
alue(32, ‘a’);
结果: value.size() == value.length()==32; value.capacity()==63;
std::string value(63, ‘a’);
结果: value.size() == value.length()==value.capacity()==63;
3. std::string value(80, ‘a’);
结果: value.size()==value.length()==80; value.capacity()==95;
std::string value(95, ‘a’);
结果: value.size()==value.length()==value.capactiy()==95;
举这3个例子不难发现
a) . size() 和 length() 效果一样,不过C++的话,倾向于用 size();
b) . string的容量,也就是capactiy(),如果 value值为空,则capactiy()==0;
否则,capacity() 初始值为32,根据string 存储的量的变化而变化
初始值=31,步长=32;
【原创】点评
当然,这个各个机器不一样,比如有初始值=15,步长=16。至于为什么31,而不是32,因为\0原因。字符串前后都有\0,但是无法利用*(s.end());*(s.rend())取出,可以用operator[]查看。
- C++容器中 size(), capacity, reserve() ,resize() 函数讲解.http://blog.csdn.net/youxin2012/article/details/9213539
【原创】点评
关于size和capacity,以及reserve()讲解的不错,遗憾的是对于resize()没有结合实例,进行深入讲解。
附件3:扩展阅读(自我总结)
好好总结一下size,capacity
至于size(),capacity()的大小关系,以及相应的编译器的分配原则,上面的参考资料已经讲解的很清楚。我这里主要介绍关于capacity,size(等价于length,string为了统一性所以加入size()。由于length()之前就有,加上名字也很合适,造成了一些冗余尴尬)。
由于string类里面关于capacity的相关操作比较全,我们就以capacity进行总结。主要涉及9个成员函数,详细见下图。
(1)首先,介绍有return值得几个成员函数。
size(),length() //等价,返回字符串大小;
capacity() //返回分配的内存空间的大小。这个不是指分配给对象string s的大小,而是对象里面有个指针,也就是s.c_str()所指向的对象所获得内存空间
max_size() //值就是npos-1 为什么不是npos?可能是为了存放\0吧。 可能值4294967294,不同编译器值可能不一样 这个无需深究
(2)接下来,来看看与size直接相关的成员函数
resize()
void resize (size_type n); //n小于目前的size,则截断;如果大于,则使用\0进行初始化多出来的元素
void resize (size_type n, charT c); //n小于目前的size,则截断;如果大于,则使用c进行初始化多出来的元素
两个函数原型。
对于size,capacity的影响。
- 对size,直接进行影响,完全控制size的取值。
- 对capacity,如果size变小,没有任何影响;如果size变大,可能会引起capacity的改变。
PS: resize()常用来快速初始化数组,尤其是二维数组。
相关测试程序如下,下面几个成员函数,只要在这个程序的基础上,加加减减就行,所以就不再贴出来。
自己可以跑一下,改改数据。
#include <iostream>
#include <string>
using namespace std;
int main()
{
std::string str(200,'\0');
std::cout << "size1: " << str.size() << "\n";
std::cout << "length1: " << str.length() << "\n";
std::cout << "capacity1: " << str.capacity() << "\n";
std::cout << "max_size1: " << str.max_size() << "\n";
str.resize(3);
std::cout << "size2: " << str.size() << "\n";
std::cout << "capacity2: " << str.capacity() << "\n";
std::cout << std::string::npos << std::endl;
return 0;
}
(3)接下来,继续看看与capacity直接相关的几个成员函数
reserve()
void reserve (size_type n = 0);
由上面描述可知,对于size,capacity的影响。
- 对size,无法进行任何影响。
- 对capacity,只增不减。
string a{"I Love You"};
a.reserve(); //no effect
a.reserve(0); //no effect
a.reserve(100); //size no change; capacity rise to more than 100 and nearest to 100 就是大于100也是最靠近100的capacity 我的VS结果是111
PS:由于push_back(),超过capacity时,会出现销毁旧内存,重新申请内存等耗时操作。因此,对于多次的push_back(),可以提前指定capacity,这样可以节省运行时间。该技巧在leetcode上面,对于提升性能,效果明显。
void shrink_to_fit(); C++11
由于reserve()对capacity的限制,且shrink_to_fit无参数传入,可是理解shrink_to_fit对于capacity只减不增。
由上面描述可知,对于size,capacity的影响。
- 对size,无法进行任何影响。
- 对capacity,只减不增。但不是减到与size相等,而是大于size,且最近size的capacity。
因此,可以得出结论,capacity的值都是系统指定的,我们无法直接控制,但是可以间接控制范围。
(4)最后,介绍几个相关的操作
bool empty() const; //看的是size,而不是capacity。由于是const函数,所以不改变size跟capacity.
void clear(); //size to 0; capacity no change
参考资料:
cplusplus.com
Constructive comments and reports of errors are always welcome.
Written by Josan.
2016/12/19