题目描述
在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
数据范围:矩阵的长宽满足 0<=n, m<=500 , 矩阵中的值满足 0≤val≤10^9
进阶:空间复杂度 O(1) ,时间复杂度 O(n+m)示例1
输入:
7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]返回值:
true说明:
存在7,返回true示例2
输入:
1,[[2]]返回值:
false示例3
输入:
3,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]返回值:
false说明:
不存在3,返回false
这题有个解法:
暴力搜索
解题思路: 逐行逐列的搜索二维数组,判断是否存在目标值。复杂度分析:
时间复杂度:O(MN)
空间复杂度:O(1)class Solution { public: bool Find(int target, vector<vector<int> > array) { for(auto i : array)//c++11语法 { for(auto j : i) { if(j == target) return true;//找到目标值 } } return false; } };
其中使用了auto
auto : 类型推导. 在使用c++的时候会经常使用, 就像在考虑STL时迭代器类型, 写模板的时候使用auto能少写代码, 也能帮助我们避免一些隐患的细节.
auto初始化
- 使用
auto
型别推导要求必须在定义时初始化, 毕竟需要根据对象的类型推导左值对象的型别.
- 但是
auto
型别推导会忽略引用和顶层const, 所以要对对象加上想要的修饰.
- C11之前只能通过
()
和=
对变量初始化, C++11增加了对定义的对象初始化的方法,可以使用{}
对变量初始化
C++ 11之前的auto
早在C++ 98标准中就存在了auto关键字,那时的auto用于声明变量为自动变量(自动变量在控制流进入变量作用域时系统自动为其分配存储空间,并在离开作用域时释放空间),拥有自动的生命周期。但是该作用是多余的,变量默认拥有自动的生命周期。
int a = 10; // 自动生命周期
auto int a = 10; // 自动生命周期
在C++ 11中,已经删除了该用法,取而代之的作用是:自动推断变量的类型。
C++ 11及以后的auto
自从C++ 11开始,编译器可以使用auto关键字自动推断被初始化的变量的类型。auto关键字也被称为占位类型说明符(placeholder type specifier)。
通常用法
auto
从初始值的类型推断变量本身的类型,变量得到一个初始值的副本。
int i;
auto j = i; // j is int contains a copy of i
auto
会将引用类型推断为值类型。为了确保是引用类型,使用auto&
。
int m = 0;
int& i = m; // i is an alias to m
auto j = i; // j is int contains a copy of i
auto& k = m; // k is int& and alias to m
auto
不能推断出const
修饰符,为了确保可以推断出,使用const auto
。
int i =0;
const int m = 0;
auto j = m; // j is int (non-const)
const auto k = m; // k is const int
const auto l = i; // l is const int, holds a copy of i
auto&
可以推断出const
修饰符。
const int i =0;
auto& j = i; // const int&, alias for i
字面常量(Literals)
auto
可以识别出以下类型:
auto i = 1; // int
auto j= 7ul; // unsigned long
auto x = 2.0; // double
auto y = 2.0f; // float
auto c = 'A'; // char
auto s = "hi"; // char const*
auto b = true; // bool
从C++ 14开始,可以识别出字符串常量:
using namespace std; // This is necessary
auto s = "hellow"s // std::string, note the s operator.
从C++ 23开始,可以识别出size_t
和有符号的size_t
类型。
auto k = 1uz; // size_t
auto m = 1z; // signed size_t
类
可以识别出自定义的类:
class LongNameClass {};
auto a = new LongNameClass(); // a is LongNameClass*
可以识别出list类型:
auto l = {1,2,3}; // l is std::initializer_list<int>
类型名字比较长的类型也可以被auto
轻松代替:
std::map<std::string, std::string> m;
auto n = m;
auto p = std::make_unique<int>(); //unique pointer
std::vector<double> vec(100);
auto& r = vec;
vector的iterator类型也可以被代替:
// type of i is std::vector<double>::iterator
for (auto i = vec.begin(); i != vec.end(); i++){
cout<< *i;
}
也可以很容易获得vector中的元素:
std::vector<unsigned long long int> vec = {1, 2, 3};
for (auto& x : vec)
cout << x ;
auto与for
auto
最常见的就是与for
联用, 特别是类型特别复杂的时候. 但是auto又有多种选择, 如 : auto, auto &等, 不同的选择其效率也不一样.
auto
, 即 for(auto i:range) . 这使range中的每一个元素都会产生一个副本, 所以即使修改了 i 也不会实际影响到range.const auto
, 及for(const auto i : range). 这也会是range的每一个元素产生一个副本, 但是这个副本竟不能被修改.auto &
, 即for(auto &i : range). 引用, 因为i 直接引用range里面的元素, 所以并不会产生一个副本, 但是 i 的修改也会影响range里元素的值. 通常我们需要修改range是会考虑用到.const auto&
, 即for(const auto &&i : range). i 直接引用range里面的元素, 所以并不会产生一个副本, 并且i 也不能修改. 一般初始化的是一个左值时而且是读取range里的元素时都是用const auto&
而不用auto
, 因为前者不会产生副本, 效率要高. 当然一般初始化的是一个左值时效率低, 但是如果是右值还是使用const auto
效率高, 因为const auto &
需要把 i 存储在内存中的一个位置,间接访问会更消耗时间auto&&
, 即for(auto &&i : range). 如果初始化是左值, 那么 i 就是左值引用, 如果初始化是右值, 那么 i 就是右值引用,
还有const auto &
, 当然具体的选择还是看具体的情况而定.
最后, 当用auto
推导多维数组的时, 保证除最内层循环外, 其他的外层循环都应该是引用类型, 否则很容易出错, 即 :
int a[10][10][10];
for (const auto &i : a)
for(const auto &j : i)
for(const auto k : j)
;
最好使用auto型别推导
1. 初始化
- 在定义对象的时候可能或多或少会忘记对变量进行初始化, 但当我们使用该变量的时候就会出错, 而且问题也不好找出来, 但是使用auto定义对象就要求必须初始化有时还能减少代码量, 上面我们已经分析过了.
- 使用
auto
初始化在平台上还有一点好处, 比如 :
vector<int> v;
unsigned size = v.size(); // size()返回size_t型别
auto sizet = v.size();
在32的平台unsigned
代表的是32位, size_t
是32位, 在64的平台unsigned
代表的也是23位, 但是size_t
却是64位, 这样平台差异可能就会带来问题, 使用auto
代替就没有这样的问题.
不过只有这几点可能不会让人心动, 下面我们还有auto的好处.
2. STL使用型别推导
还记得在前言中个说过调用STL最好使用auto
推导型别, 如果你还记得map
与pair
吗? 是这样 map<pair<key, type>>
? 还是map<pair<const key, type>>
? 答案是最后一种, 那么现在我们就来分析的使用auto
推导还是显示型别比较好.
看到上面的例子毫无问题, 但是深究起来显示型别还是些不完美. 我们知道map的key
不能被改变, 所以显示型别的string
与map的const string
不是匹配, 编译器就会将map对象都会产生一个临时对象再隐式的转为string
, 等等. 是不是注意到有一点了, 为了型别匹配赋值会产生临时变量, 那岂不是每一循环都会产生一个临时变量, 但是auto
型别推导就是精确匹配的, 不会产生临时变量.
可能觉得将显示型别的key改为const string
就能解决这个问题了, 确实是这样, 但是如果没有注意到这一点细节, 那就会损失效率了, 使用auto可以完全不想这些问题啊.
总结
本节对C11的auto用法做了一个浅显的分析, 分别对使用auto
的好处, 定义时注意{}
对象也必须初始化, auto
在与for连用的时候要根据实际参数确定选择哪种实现, 这样效率才会达到最大, 当然一般都使用const auto&
和auto&&
. 最后还对auto
与函数返回值关联, 可以将返回型别放在函数名尾也可以, 这样的做法一般在模板中将模板参数作为返回值才考虑用, 平时也不必这样定义函数.
参考:
C++ auto keyword makes your life easier
C++ keyword: auto - cppreference.com
Placeholder type specifiers (since C++11) - cppreference.com