题目
设计并实现一个 TwoSum 类。他需要支持以下操作:add 和 find。
add -把这个数添加到内部的数据结构。
find -是否存在任意一对数字之和等于这个值
每次操作的数的绝对值小于 10^6
样例
样例 1:
add(1);add(3);add(5);
find(4)//返回true
find(7)//返回false
分析
这道题是一道设计类的题
我们先来想最简单的想法
1.顺序表
add()函数,添加时直接放在尾部
时间复杂度O(1)
find()函数,查找时直接两两对比,查看是否能组合出来
时间复杂度O(n^2)
这个n^2 复杂度可以说是非常高了,我们想一下怎么来改进,如果是一个有序的数组,我们就可以利用双指针来做这道题
add()函数,每次添加的时候做一次插入排序
时间复杂度O(n)
思考一下我们可以利用二分查找,找到能插入的位置,这样是不是就会降为logn的时间复杂度了,事实不是这样的,当找到插入位置时,后面的元素向后移动花费的时间复杂度仍然是n
find()函数,一个指针指向头部,一个指针指向尾部,每次比较移动一个指针
时间复杂度O(n)
跟之前的比确实有所改进
2.链表
find()函数跟顺序表的时间复杂度是一样的
之前说到顺序表的add()函数,利用二分法但是插入时需要n的时间复杂度,链表的插入只需要O(1)的时间,这样是不是就会有所改进,事实也不是这样的,因为链表没法利用二分法,二分法是基于数组的下标的
我们思考,如何能提高查找插入的正确位置更快,如何插入的速度更快呢,这时我们想到了哈希表
3.哈希表
这里我们直接利用c++自带的map
find()函数
我们map<int,int>key为储存的数字的值,value为当前数值的数字的个数
我们只需要将查找的元素减去map中的每个值,查找他们的差是否存在即可
时间复杂度是O(n)吗,还没有算查找的时间,这里用的是map,所以时间复杂度为O(nlogn)
插入需要的时间O(1),查找位置需要的时间O(1)
所以这里的时间是O(1)吗
事实并不是的,我们这里虽然用的是hash_map但是事实上它并不是哈希表,它的底层是红黑树
哈希表的话添加元素当然是O(1)
红黑树的插入操作时间复杂度为O(logn)
所以这里的时间复杂度为O(logn)
代码部分
class TwoSum {
public:
map<int,int> hash_map;
void add(int number) {
if(hash_map.find(number)!=hash_map.end()) //存在
{
hash_map[number]++;
}
else
{
hash_map.insert(make_pair(number,1));
}
}
bool find(int value) {
for(auto it=hash_map.begin();it!=hash_map.end();it++)
{
int num=value - it->first;
int cnt= num==it->first?2:1;
// cout<<value<<" "<<num<<" "<<cnt<<endl;
if(hash_map.find(num)!=hash_map.end())
{
if(hash_map[num]>=cnt)
return true;
}
}
return false;
}
};
总结
顺序表
添加元素的时间复杂度为O(n),查找元素的时间复杂度为O(logn)
链表
添加元素的时间复杂度为O(1),查找元素的时间复杂度为O(n)
相比之下顺序表更适合查找元素,链表更适合添加元素
哈希表
添加元素的时间复杂度为O(1),查找元素的时间复杂度为O(1)
但是哈希表的空间复杂度很高,底层原理为散列表,当数据非常分散的时候会造成大量的空间浪费
红黑树(c++自带的map)
添加元素的时间复杂度为O(logn),查找元素的时间复杂度为O(logn)
相比之下,利用红黑树可能更全面一些
当一个元素需要查找它特定的位置并且插入的时候,红黑树是最好的选择