C++算法之数据结构三
作者:互联网
数据结构
1.哈希表
哈希表,又称散列表,使用 O(n) 空间复杂度存储数据,通过哈希函数映射位置,从而实现近似 O(1) 时间复杂度的插入、查找、删除等操作。
C++ 中的哈希集合为 unordered_set,可以查找元素是否在集合中。如果需要同时存储键和值,则需要用 unordered_map,可以用来统计频率,记录内容等等。如果元素有穷,并且范围不大,那么可以用一个固定大小的数组来存储或统计元素。例如我们需要统计一个字符串中所有字母的出现次数,则可以用一个长度为 26 的数组来进行统计,其哈希函数即为字母在字母表的位置,这样空间复杂度就可以降低为常数。
一个简单的哈希表的实现如下。
template <typename T>
class HashTable{
private:
vector<list<T>> hash_table;
//哈希函数
int myhash(const T & obj)const{
return hash(obj,hash_table.size());
}
public:
//size最好是质数
HashTable(int size=31)
{
hash_table.reserve(size);
hase_table.resize(size);
}
~HashTable() {}
//查找哈希表是否存在该值
bool contains(const T & obj)
{
int hash_value = myhash(obj);
const list<T> & slot = hash_table[hash_value];
std::list<T>::const_iterator it =slot.cbegin();
for(;it!=slot.cend() && *it != obj;++it);
return it != slot.cend();
}
//插入值
bool insert(const T & obj)
{
if(contains(obj))
{
return false;
}
int hash_value=my_hash(obj);
std::list<T>& slot = hash_table[hash_value];
slot.push_front(obj);
return true;;
}
//删除值
bool remove(const T & obj)
{
list<T>& slot = hash_table[myhash(obj)];
auto it=find(slot.begin(),slot.end(),obj);
if(it == slot.end())
{
return false;
}
slot.erase(it);
return true;
}
};
//一个简单的对整数实现的哈希函数
int hash(const int & key,const int &tableSize)
{
return key%tableSize;
}
如果需要大小关系的维持,且插入查找并不过于频繁,则可以使用有序的 set/map 来代替
unordered_set/unordered_map。
题目一:给定一个整数数组,已知有且只有两个数的和等于给定值,求这两个数的位置。
可以利用哈希表存储遍历过的值以及它们的位置,每次遍历到位置 i 的时候,查找哈希表里是否存在 target - nums[i],若存在,则说明这两个值的和为 target。
vector<int>twoSum(vector<int>&nums,int target)
{
//键是数字,值是该数字在数组中的位置
unordered_map<int,int>hash;
vector<int>ans;
for(int i=0;i<nums.size();++i)
{
int num=nums[i];
auto pos = hash.find(target - num);
if(pos == hash.end())
{
hash[num]=i;
}
else{
ans.push_back(pos->second);
ans.push_back(i);
break;
}
}
return ans;
}
题目二:给定一个整数数组,求这和数组中的数字可以组成的最长连续数列有多长
可以把所有数字放到一个哈希表,然后不断地从哈希表中任意取一个值,并删除掉其之前之后的所有连续数字,然后更新目前的最长连续序列长度。重复这一过程,我们就可以找到所有的连续数字序列
int longestConsecutive(vector<int>&nums)
{
unoedered_set<int>hash;
for(const int & num: nums)
{
hash.insert(num);
}
int ans=0;
while(!hash.empty())
{
int cur = *(hash.begin());
hash.erase(cur);
int next = cur + 1;
int pre = cur - 1;
while(hash.count(next))
{
hash.erase(next++);
}
while(hash.count(prev))
{
hash.erase(prev--);
}
ans = max(ans,next - prev - 1);
}
return ans;
}
题目三:给定一些二维坐标中的点,求同一条线上最多由多少点。
对于每个点,我们对其它点建立哈希表,统计同一斜率的点一共有多少个。这里利用的原理是,一条线可以由一个点和斜率而唯一确定。另外也要考虑斜率不存在和重复坐标的情况。
本题也利用了一个小技巧:在遍历每个点时,对于数组中位置 i 的点,我们只需要考虑 i 之后的点即可,因为 i 之前的点已经考虑过 i 了。
int maxPoints(vector<vector<int>>&points)
{
unordered_map<double,int>hash;//斜率-点个数
int max_count = 0, same = 1, same_y = 1;
for(int i=0;i<points.size();++i)
{
same = 1,same_y = 1;
for(int j=i+1;j<points.size();++j)
{
if(points[i][1] == points[j][1])
{
++same_y;
if(points[i][0] == points[j][0])
{
++same;;
}
}
else{
double dx=points[i][0] - points[j][0];
double dy=points[i]p1] - points[j][1];
++hash[dx/dy];
}
}
max_count = max(max_count,same_y);
for(auto item:hash)
{
max_count = max(max_count,same+item.second);
}
hash.clear();
}
return max_count;
}
2.多重集合和映射
题目一:给定一个人坐过的一些飞机的起止机场,已知这个人从 JFK 起飞,那么这个人是按什么顺序
飞的;如果存在多种可能性,返回字母序最小的那种。
先用哈希表记录起止机场,其中键是起始机场,值是一个多重集合,表示对应的终止机场。因为一个人可能坐过重复的线路,所以我们需要使用多重集合储存重复值。储存完成之后,我们可以利用栈来恢复从终点到起点飞行的顺序,再将结果逆序得到从起点到终点的顺序。
vector<string>findItinerary(vector<vector<string>>&tickets
{
vector<string>ans;
if(tickets.empty())
{
return ans;
}
unordered_map<string,multiset<string>>hash;
for(const auto & ticket: tickets)
{
hash[ticket[0].insert(ticket[1]);
}
stack<string>s;
s.push("JFK");
while(!s.empty())
{
string next = s.top();
if(hash[next].empty())
{
ans.push_back(next);
s.pop();
}
else{
s.push(*hash[next].begin());
hash[next].erase(hash[next].begin());
}
}
reverse(ans.begin(),ans.end());
return ans;
}
3.前缀和积分图
一维的前缀和,二维的积分图,都是把每个位置之前的一维线段或二维矩形预先存储,方便加速计算。如果需要对前缀和或积分图的值做寻址,则要存在哈希表里;如果要对每个位置记录前缀和或积分图的值,则可以储存到一维或二维数组里,也常常伴随着动态规划。
题目一:设计一个数据结构,使得其能够快速查询给定数组中,任意两个位置间所有数字的和。
对于一维的数组,我们可以使用前缀和来解决此类问题。先建立一个与数组 nums 长度相同的新数组 psum,表示 nums 每个位置之前前所有数字的和。psum 数组可以通过 C++ 自带的partial_sum 函数建立,也可以直接遍历一遍 nums 数组,并利用状态转移方程 psum[i] = psum[i-1] + nums[i] 完成统计。如果我们需要获得位置 i 和 j 之间的数字和,只需计算 psum[j+1] - psum[i]即可。
class NumArrray
{
vector<int>psum;
public:
NumArray(vector<int>nums): psum(nums.size()+1,0){partical_sum(nums.begin(),
nums.end(),psum.begin()+1);}
int sumRange(int i,int j)
{
return psum[j+1] - psum[i];
}
};
题目二:设计一个数据结构,使得其能够快速查询给定矩阵中,任意两个位置包围的长方形中所有数
字的和。
class NumMatrix{
vector<vector<int>>integral;
public:
NumMatrix(vector<vector<int>>matrix)
{
int m = matrix.size();
int n = m >0 ? matrix[0].size(): 0;
integral = vector<vector<int>>(m + 1,vector<int>(n+1,0));
for(int i=1;i<=m;++i)
{
for(int j=1;j<=n;++j)
{
integral[i][j] = matrix[i-1][j-1] + integral[i-1][j] +
integral[i][j-1] - integral[i-1][j-1];
}
}
}
int sumRegion(int row1,int col1,int row2,int col2)
{
return integral[row2+1][cil2+1] - integral[row2+1][col1] -
integral[row1][col2+1] + integral[row1][col1];
}
}
题目三:给定一个数组,寻找和为 k 的连续区间个数。
本题同样是利用前缀和,不同的是这里我们使用一个哈希表 hashmap,其键是前缀和,而值是该前缀和出现的次数。在我们遍历到位置 i 时,假设当前的前缀和是 psum,那么 hashmap[psum-k],即为以当前位置结尾、满足条件的区间个数。
int subarraySum(vector<int>& nums,int k)
{
int count = 0,psum = 0;
unordered_map<int,int>hashmap;
hashmap[0]=1;//初始化很重要
for(int i:nums)
{
psum +=i;
count += hashmap[psum-k];
++hashmap[psum];
}
return cout;
}
标签:hash,nums,int,C++,算法,哈希,return,数据结构,psum 来源: https://blog.csdn.net/yohnyang/article/details/120335868