LeetCode上查找问题很多,比如第一题,两数之和,找到两个数字和为target
![f76805b9639422b4b09982143deab149.png](https://i-blog.csdnimg.cn/blog_migrate/f285bf27728bf53bc87c7f6012bc4165.png)
暴力枚举的方法就需要O (N^2) 先排序再用双指针的方法也需要O(NlogN)然而查找表的方法只需要O(n) ,我和我的小伙伴们都惊呆了。
可以说,优化了一个阶层,效率十分之高。
那,什么是查找表?什么时候用?以及,查找表怎么用?
下面我一一分析:
查找表,就是用来查找的表咯。 对于要查找的数据,我全部放入查找表中,时间复杂度是O(n),再根据条件一一筛选,时间复杂度也是O(n)得出答案。
举个例子,两数之和。 找到一个数组中的两个数和为目标值(以给定)。首先将数组的所有数,都是可能的结果,放入查找表中。O(n) ,再遍历这个数组O(n),然后根据条件筛选。
条件是啥? target-nums[i] 是否在查找表中。(nums[i]就是每一个数)
非常简单吧,和暴力遍历的方法差不多。
那为什么查找表的方法能优化到O(n)呢?
这就和查找表的底层实现有关了。
因为,我这里使用的查找表用unordered_map实现,也就是哈希表。
我们知道,哈希表查找元素的时间复杂度是O(1) 非常的Amazing啊!!!
发现没有?优化的地方仅仅是查找中的O(n)优化成O(1),本来结果是O(N*N)
现在变成O(n*1)
这就是查找表的本质。
看出了他的本质,再利用他解题就轻而易举了。
回答第二个问题:什么时候用查找表?
这tm还用问吗?
查找的时候啊。
当你需要在数组,string vector等序列中,找到你要的答案,此时,查找表就可以派上用场。
最后一个问题:怎么用?
这个问题太关键了,听我慢慢道来。
先回忆一下,第一个问题中,我解释的查找表的原理。还记得吗?不记得倒回去看看。
我们仅仅优化了查找的部分。
其他部分和暴力遍历是一样的。
那么,代码写成暴力遍历的样子就行了,最后稍加修改就大功告成了。
什么?很抽象?
举个栗子。
这是两数之和的代码:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> table;
vector<int> ret;
for(int i=0;i<nums.size();i++){
table[nums[i]]=i;
}
for(int i=0;i<nums.size();i++){
int tmp=target-nums[i];
if(table.count(tmp) && table[tmp]!=i){
ret.push_back(i);
ret.push_back(table[tmp]);
return ret;
}
}
return ret;
}
};
只看这一段:
for(int i=0;i<nums.size();i++){
int tmp=target-nums[i];
if(table.count(tmp) && table[tmp]!=i){
ret.push_back(i);
ret.push_back(table[tmp]);
return ret;
}
}
外面的循环遍历了这个数组,里面本来是要循环查找的,但是我们有了查找表就只需要把里层循环换成查找就行了。
在LeetCode上看见一位大神的比喻十分生动:
查找问题,就相当于配对,两个人相亲。 对于某个人i,找到和他相配的人。
首先,我们把所有人都登记一下,记录他喜欢什么样的妹子。(放入查找表里)
然后遍历整个相亲大会,对于每一个人,在你登记的表里(查找表)去里面找到你喜欢的那个。
很形象的解释了。
有没有更无脑的模板?
这个可以有,我给大家总结了了一份。
1.遍历序列,将每一个元素,放入查找表中,记录信息。
2.遍历序列,对于每一个元素,在查找表中找到你需要的元素。
说了辣么多,搞点题目做一下啦。
力扣leetcode-cn.com![aeb6e7989e804007064ed523b57e596d.png](https://i-blog.csdnimg.cn/blog_migrate/83561e30e60a21e2f04d0609a3408d50.jpeg)
让我抽丝剥茧一下,把这个问题的本质抽象出来。
给的序列是一个点集。这个序列中有所有的点坐标,对于每一个点坐标i,我们要找到一些点坐标,其中每一个点坐标到i的距离都相同。
本质还是查找问题。
如果是暴力解法,我们需要枚举三个点,O(n^3),查找表,是O(n^2)
关键是:放入查找表的信息是啥? 判断是结果的条件是啥?
因为我们要查找的是距离相同的点,那么,对于遍历到的每一个点坐标i,都存在一些点,到他的距离相同。我们只需要遍历每一个点,然后产生一个查找表,到这个点的距离作为key
有多少个这样的点,也就是距离相等的点的个数作为value。
这张表构建完成之后,就有所有信息了。
然后遍历这张表,对于每个距离,都统计个数。
生成一张表O(n)
每个元素都要一张,O(n*n)=O(n^2)
因为(需要考虑元组的顺序)。
所以,如果对于到i这个点的距离相等的点只有一个,那么就无法构成答案,
并且考虑顺序的条件下,如果有n个,就需要从三个中选出两个组成答案,总共有
![9c9846349919a1cf7abb47c81049686e.png](https://i-blog.csdnimg.cn/blog_migrate/4db347eea99ce18b96ee0b14c32e9de7.png)
种。
计算结果是N*(N-1),然而这样的方法提交死活超时。
统计结果的时候用了这个公式:
![f8bea548aa9cfc5d34964ce988dafff5.png](https://i-blog.csdnimg.cn/blog_migrate/a94109c5c06fa91ffac66fade5c7543f.jpeg)
class Solution {
public:
int dis(const vector<int> &a,const vector<int>& b){
return (a[0]-b[0])*(a[0]-b[0])+
(a[1]-b[1])*(a[1]-b[1]);
}
int numberOfBoomerangs(vector<vector<int>>& points) {
int res=0;
unordered_map<int,int> record;
for(int i=0;i<points.size();i++){
record.clear();
for(int j=0;j<points.size();j++){
int dis=pow(points[i][0]-points[j][0],2)+pow(points[i][1]-points[j][1],2);//取距离的平方,不开根号,避免了浮点数的误差问题
if(j!=i){
record[dis]++;
}
if(record[dis]>1) res+=2*(record[dis]-1);
}
}
return res;
}
};
![9d52df950e4dd26812be2e3364259dd8.png](https://i-blog.csdnimg.cn/blog_migrate/c822f82c3bfe755d7eede32df3e65be6.png)
可以看出,数据量是挺大的。