真是蛋疼啊,今天打开牛客网,准备写一道生成Gray码的题目,结果居然发现了一个坑。
题目:
在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同, 则称这种编码为格雷码(Gray Code),请编写一个函数,使用递归的方法生成N位的格雷码。
给定一个整数n,请返回n位的格雷码,顺序为从0开始。
测试样例:
1
返回:["0","1"]
要求用递归,我的想法:
1位: 0,1
2位 :00 ,01 ,11 ,10
3位: 000, 001 ,011, 010 , 110 ,111 ,101 ,100
Gray(2) -> Gray(3) 00 ,01 ,11 ,10每个前面加个0,变为000 ,001 ,011 ,010,把10,11,01,00前面加个1,变为110,111 ,101 ,100。就ok了。
class GrayCode {
public:
vector<string> getGray(int n) {
if (n <= 0) {
return {};
}
if (n == 1) {
return { "0","1" };
}
vector<string> v(getGray(n - 1));
v.reserve(v.size() << 1);
for(auto it = v.rbegin(); it != v.rend(); ++it){
string& s = *it;
v.emplace_back("1" + s);
s.insert(s.begin(), '0');
}
return v;
}
};
思路就是这样的,因为有插入动作,所以我先reserve了一下vector,想以此保证迭代器不失效。it 是 rbegin -> rend, 从右往左;而emplace_back是从左往右。
结果还是rbegin失效了。。
所以我就有点懵逼,问了一下同学,同学也说应该没问题,c++ primer里也有说明:
调试了一下,发现这个说法,对普通的iterator是对的,但是reserve_iterator(准确来讲是rbegin()返回的迭代器,比如代码里it)不一样,任何插入都会导致其失效。
调试:
源码里emplace_back无效iterator的地方:
it = v.rbegin(),实际it里面记录的是vector中end()返回的迭代器,放在其成员变量current里。
vector中的rbegin函数定义:
reserve_iterator的构造:
对于reserve_iterator的引用,++,--,都是对current操作:
显然,emplace_back会将之前的v.end()返回的迭代器内容无效,这就导致current无效了,因此,++it 出现异常。
https://stackoverflow.com/questions/40581249/how-to-use-vectortreverse-iterator-with-one-element 这里与David Scarlett的说法是一致的。