本篇文章讲讲面试过程中C++手撕leetcode环节多数人使用C++做题的高频易错地方,本质上是由于对C++不熟悉而导致的python和C++语法混用。本文首发于我的公众号“AI不止算法”,文章链接在此
为什么要总结STL常用用法
对STL容器、算法、迭代器等的使用在C++类工作的面试撕题中基本跑不掉,这很容易看出一个候选人的coding能力,同时日常工作中也会经常使用,可以达到简化某些逻辑的目的,上篇也已经简单介绍过动机,本文接着上篇继续总结本人认为经常用到的一些STL用法,更加侧重于算法函数,同时也作为自己的一个复习笔记~
算法
常用的算法基本都位于头文件中
填充类:
- fill(begin, end, v):填充某容器的值全为v,源码如下,非常简单有趣,把迭代器和值模板化,这样就使得fill可以应用在任意容器和任意类型的scalar
template< class ForwardIt, class T >
void fill(ForwardIt first, ForwardIt last, const T& value)
{
for (; first != last; ++first) {
*first = value;
}
}
//用法也比较简单
std::vector<int> a;
std::fill(a.begin(),a.end(),6);
- generate(begin, end, func)/generate_n(begin, n, func) :使用func生成数据填充到某容器的begin到end或者前n个位置中,比如以下根据斐波那契数列规则填充10个元素到vector,这个我个人感觉貌似用的不多,当然可能是我个人用的少的原因
class Fibonacci{
int f1;
int f2;
public:
Fibonacci(int start1, int start2){
f1 = start1;
f2 = start2;
}
int operator()(){
int r = f1 + f2;
f1 = f2;
f2 = r;
return r;
}
};
int main(){
vector<int> v1(10);
generate(v1.begin(), v1.end(), Fibonacci(0, 1));
}
generate/generate_n的源码也很有趣,很好理解,把迭代器和生成器通过模板泛化,这样就使得generate可以应用在任意容器和任意函数
template <class ForwardIterator, class Generator>
void generate ( ForwardIterator first, ForwardIterator last, Generator gen )
{
while (first != last) {
*first = gen();
++first;
}
}
template <class ForwardIterator, class Generator>
void generate_n ( ForwardIterator first, size n, Generator gen )
{
while (n>0) {
*first = gen();
++first; --n;
}
}
变换类
- transform
我自己是非常喜欢用transform函数,因为非常简洁省事,支持unary和binary操作,并且支持inplace,当我们想要对某个容器做某种修改操作时,这可以替代掉很多繁重的for循环,先看看源码声明,上面是unary操作时的写法,下面是binary操作时的写法,意思是对first1到last1的所有元素做unary op操作,或者对两个input的所有元素做binary op操作,通过fill和generate的源码,我们大致可以猜出transform的源码:
template <class InputIterator, class OutputIterator, class UnaryOperation>
OutputIterator transform (InputIterator first1, InputIterator last1,
OutputIterator result, UnaryOperation op){
while (first1 != last1) {
*d_first++ = unary_op(*first1++);
}
return d_first;
}
template <class InputIterator1, class InputIterator2,
class OutputIterator, class BinaryOperation>
OutputIterator transform (InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, OutputIterator result,
BinaryOperation binary_op){
while (first1 != last1) {
*d_first++ = binary_op(*first1++, *first2++);
}
return d_first;
}
来几个例子感受一下
//uanry例子:把每个字符都大写,并且大写后的结果原地保存在s
std::string s("Hello");
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return std::toupper(c); });
std::cout << s << std::endl; // HELLO
// binary例子:把foo和bar两个vector相加结果保存在foo,结果为5个3
std::vector<int> foo(5, 1);
std::vector<int> bar(5, 2);
// std::plus adds together its two arguments:
std::transform(foo.begin(), foo.end(), bar.begin(), foo.begin(), std::plus<int>());
区间最大最小值
-
签名:max_element/min_element(iterator1/address, iterator2/address)
-
返回值:iterator或地址,加上解引用*即得最大最小值
-
优点:当我们要求某个区间范围内的最大最小值或者最大最小值的index时, max_element和min_element直接传入这个区间的起始地址即可,会比循环max/min方便很多,尤其是数组是多维的时候。比如leetcode 120题的官方解法就用到了
-
例子:
int a[] = {1, 2, 3, 4};
int maxPosition = max_element(a,a + 2) - a; //最大值下标
int max_val = *max_element(a,a + 2); //最大值
vector<int> n = {1, 2, 3, 4};
int maxPosition = max_element(n.begin(), n.end()) - n.begin(); //最大值下标
int max_val = *max_element(n.begin(), n.end());//最大值
复制函数
-
签名:copy(iterator1, iterator2, dst_iterator)
-
类似函数:copy和memcpy都是copy,但是他们的最大区别是memcpy是byte by byte的copy,而copy是ele by ele的copy,我们很多时候可能更多的是想做后者,但是却用成了前者,这也是很多memcpy相关bug的发生地
-
例子:把a的三个元素从temp的首地址一直copy
std::vector<int> temp(3);
int a[3] = {1, 2, 3};
std::copy(a, a+3, &temp.front());
总结
还有非常多有用的算法函数没有列出,总的来说,从以上的叙述可以看出,都符合一个规律:他们都可以很方便地对某个区间或者范围做某种操作,只需要指定起始地址和结束地址,并且传入相应的value或者function去操作就好了。
最后,欢迎关注我的公众号“AI不止算法”。