编程语言
首页 > 编程语言> > C++算法之数据结构三

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