代码随想录day7哈希表2

c++中 int, long long, double 等数据类型的长度及范围整理

在这里插入图片描述
你最多能定义的局部数组大小(栈上)大约是:

最大个数 ≈ 栈大小 / sizeof(int)8 * 1024 * 1024 / 42 * 1024 * 1024 = 2,097,152int

为了稳妥,建议不要超过:

const int N = 2e6;  // 即 2,000,000
int a[N];           // 栈上安全范围内
const int N = 1e6;
long long a[N];  // ✅ 一般栈上没问题

在算法竞赛中,我们常常需要用到设置一个常量用来代表“无穷大”。

比如对于int类型的数,有的人会采用INT_MAX,即0x7fffffff作为无穷大。但是以INT_MAX为无穷大常常面临一个问题,即加一个其他的数会溢出。

而这种情况在动态规划,或者其他一些递推的算法中常常出现,很有可能导致算法出问题。

所以在算法竞赛中,我们常采用0x3f3f3f3f来作为无穷大。0x3f3f3f3f主要有如下好处:

0x3f3f3f3f的十进制为1061109567,和INT_MAX一个数量级,即109数量级,而一般场合下的数据都是小于109的。
0x3f3f3f3f * 2 = 2122219134,无穷大相加依然不会溢出。
可以使用memset(array, 0x3f, sizeof(array))来为数组设初值为0x3f3f3f3f,因为这个数的每个字节都是0x3f。

哈希表的定义

模拟散列表

(1)哈希表的作用
    哈希表就是在关键字和存储位置之间建立对应关系,使得元素的查找可以以O(1)的效率进行, 其中关键字和存储位置之间是通过散列函数建立关系,记为:
在这里插入图片描述
(2) 常见的散列函数
    1)线性定址法:直接取关键字的某个线性函数作为存储地址,散列函数为:
在这里插入图片描述

2)除留余数法:将关键字对某一小于散列表长度的数p取余的结果作为存储地址,散列函数为:
在这里插入图片描述
    3)平方取中法:对关键字取平方,然后将得到结果的中间几位作为存储地址;
    4)折叠法:将关键字分割为几部分,然后将这几部分的叠加和作为存储地址。
(3) 地址冲突解决方法
    通过以上方法构建的哈希表在理想的情况下能够做到一个关键字对应一个地址,但是实际情况是会有冲突发生,也就是散列函数会将多个关键字映射到同一个地址上。以下是一些解决冲突的方法:
      1)开放地址法:
    线性探测法:当发生冲突时,就顺序查看下一个存储位置,如果位置为空闲状态,就将该关键字存储在该位置上,如果还是发生冲突,就依次往后查看,当查看到存储空间的末尾时还是找不到空位置,就返回从头开始查看;
    在这里插入图片描述

#include <iostream>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include<list>
#include <set>
#include <ctime>
#include<unordered_map>
#include <bitset>
#include<random>
#include<regex>
#include <chrono>
#include<unordered_map>
#include<unordered_set>
using namespace std;

typedef long long ll;
#define pr pair<double,int>
#define tp tuple<int,int,int>
const int N = 2e5 + 3,INF=0x3f3f3f3f;
const int mod = 100003;//最好是质数
ll a[N],s[N];
ll n, m, k;
ll e[N];
ll ne[N];
int h[N];
ll head,idx;

int find(ll x)
{
    ll k = (x % N + N) % N;
    while(h[k]!=x&&h[k]!=INF)
    {
        k++;
        if(k==N) k=0;
    }
    return k;
}
void solve()
{
    ll n;
    cin >> n;
    memset(h,0x3f,sizeof(h));
    while(n--)
    {
        char c;
        ll x;
        cin >> c >> x;
        ll p=find(x);
        if(c=='I')  h[p]=x;
        else {
            if(h[p]!=INF) cout << "Yes\n";
            else cout << "No\n";
        }
    }
}

 int main()
{
    cin.tie(0); 
    cout.tie(0);
    ios::sync_with_stdio(false);//提高cin、cout的输入输出效率
    solve();
}

memset(h, 0x3f, sizeof(h)) 实际做了什么?
它将数组 h 中每个字节都设置为 0x3f(即二进制的 00111111),对于 int 或 long long 而言,效果如下:

对于 int 类型(4 字节),0x3f3f3f3f ≈ 1.06e9

对于 long long 类型(8 字节),变成 0x3f3f3f3f3f3f3f3f ≈ 4.56e18

这通常被用作**“无穷大”**,但又小于 INT_MAX,适合比较和加减运算,不会溢出。

2)拉链法
    将具有相同存储地址的关键字链成一单链表, m个存储地址就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构,假设散列函数为 Hash(key) = key %13,其拉链存储结构为:
    在这里插入图片描述

#include <iostream>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include<list>
#include <set>
#include <ctime>
#include<unordered_map>
#include <bitset>
#include<random>
#include<regex>
#include <chrono>
#include<unordered_map>
#include<unordered_set>
using namespace std;

typedef long long ll;
#define pr pair<double,int>
#define tp tuple<int,int,int>
const int N = 2e5 + 3;
const int mod = 100003;//最好是质数
ll a[N],s[N];
ll n, m, k;
ll e[N];
ll ne[N];
ll h[N];
ll head,idx;
void insert(ll x)
{
    ll k = (x % N+ N) % N;//先加一个在取模,防止负数
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx++;
}
bool find(ll x)
{
    ll k = (x % N + N) % N;
    for(int i = h[k]; i != -1; i = ne[i])
    {
        if(e[i] == x)
            return true;
    }
    return false;
}
void solve()
{
    ll n;
    cin >> n;
    memset(h,-1,sizeof(h));
    while(n--)
    {
        char c;
        ll x;
        cin >> c >> x;
        if(c=='I')  insert(x);
        else {
            if(find(x)) cout << "Yes\n";
            else cout << "No\n";
        }
    }
}

 int main()
{
    cin.tie(0); 
    cout.tie(0);
    ios::sync_with_stdio(false);//提高cin、cout的输入输出效率
    solve();
}

383. 赎金信

题目链接
文章讲解

AC

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        const int N=1e5;
        int a[N];
        memset(a,0,sizeof(a));

        for(int i=0;i<magazine.size();i++)
        {
            a[magazine[i]]++;
        }
        for(int i=0;i<ransomNote.size();i++)
        {
            a[ransomNote[i]]--;
            if(a[ransomNote[i]]<0) return false;
        }
        return true;
    }
};

第454题.四数相加II

题目链接
文章讲解
视频讲解

AC

关键是把n4次方的时间复杂度减到n平方

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        int ans=0;
        unordered_map<int,int> m;
    for(int i=0;i<nums1.size();i++)
    {
        for(int j=0;j<nums2.size();j++)
        {
            m[nums1[i]+nums2[j]]++;
        }
    }
    for(int k=0;k<nums3.size();k++)
            {
                for(int p=0;p<nums4.size();p++)
                {
                    int x=-(nums3[k]+nums4[p]);
                    if(m.find(x)!=m.end()) ans+=m[x];
                }
            }
    return ans;
    }
};

第15题. 三数之和

题目链接
文章讲解
视频讲解

AC

set去重:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        set<vector<int>> s;
        vector<vector<int>> ans;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++)
        {
            int l=i+1,r=nums.size()-1;
            while(l<r)
            {
                int k=nums[i]+nums[l]+nums[r];
                if(k<0) l++;
                if(k>0) r--;
                if(k==0) {
                s.insert({nums[i],nums[l],nums[r]});
                l++;
                r--;
                };
            }
            
        }
        for(auto it=s.begin();it!=s.end();it++)
        ans.push_back(*it);
        return ans;
    }
};

剪枝去重:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> s;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]>0)  break;
            int l=i+1,r=nums.size()-1;
            if(i>0&&nums[i]==nums[i-1]) continue;//去重
            while(l<r)
            {
                int k=nums[i]+nums[l]+nums[r];
                if(k<0) l++;
                if(k>0) r--;
                if(k==0) {
                s.push_back({nums[i],nums[l],nums[r]});
                while (l < r && nums[l] == nums[l + 1]) l++;//去重
                while (l < r && nums[r] == nums[r - 1]) r--;//去重
                l++;
                r--;
                };
            }
            
        }
        
        return s;
    }
};

第18题. 四数之和

题目链接
文章讲解
视频讲解

AC

用set来去重 时间会用的更多 但也能通过
我已经long long定义k了为什么还要强制转换:
尽管你已经将 k 定义为 long long,但 加法操作中依然可能存在溢出,因为 nums[i]、nums[j]、nums[l] 和 nums[r] 本身是 int 类型,而在 int 类型的操作过程中,它们的和会先被计算为 int,然后如果超出了 int 的范围,才会出现溢出。因此,为了避免溢出,必须先将参与计算的操作数转换为 long long 类型。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        set<vector<int>> s;
        vector<vector<int>> ans;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++)
        {
            for(int j=i+1;j<nums.size();j++)
            {
                int l=j+1,r=nums.size()-1;
                while(l<r)
                {
                    long long k=(long long)nums[i]+nums[j]+nums[l]+nums[r];
                    if(k==target){
                    s.insert({nums[i],nums[j],nums[l],nums[r]});
                      l++;
                      r--;
                    }
                    else if(k>target) r--;
                    else l++;
                }
           
            }
            
        }
        for(auto it=s.begin();it!=s.end();it++)
        ans.push_back(*it);
        return ans;
    }
};

第二种方法:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> ans;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++)
        {
            if(i>0&&nums[i]==nums[i-1]) continue;
           
            for(int j=i+1;j<nums.size();j++)
            {
                if(j>i+1&&nums[j]==nums[j-1]) continue;
                
                int l=j+1,r=nums.size()-1;
                while(l<r)
                {
                    long long k=(long long)nums[i]+nums[j]+nums[l]+nums[r];
                    if(k==target){
                    ans.push_back({nums[i],nums[j],nums[l],nums[r]});
                    while(l<r&&nums[r]==nums[r-1]) r--;
                    while(l<r&&nums[l]==nums[l+1]) l++;
                      l++;
                      r--;
                    }
                    else if(k>target) r--;
                    else l++;
                }
           
            }
            
        }
       
        return ans;
    }
};
### 关于哈希表的学习资源 对于希望深入理解哈希表的人来说,代码随想录提供了丰富的学习材料。网站上的哈希表部分不仅涵盖了基础概念,还包含了实际应用案例和解题思路[^1]。 #### 哈希表的基础理论 哈希表是一种基于键值对的数据结构,它允许快速访问数据项。其工作原理是通过特定的哈希函数计算给定键对应的索引位置,并以此来存取关联的数据值。当多个不同的输入映射到相同的输出时会发生冲突,即所谓的哈希碰撞现象。处理这类情况的方法有两种主要方式:拉链法(分离链接)与开放寻址法(如线性探测)。这些基础知识可以在代码随想录有关哈希表的文章中找到详细的解释[^5]。 #### 实战练习与技巧分享 为了更好地掌握哈希表的应用场景和技术细节,在完成理论学习之后可以尝试解答一些经典的算法题目。例如,“有效的字母异位词”,这是一道考察字符串操作能力的好题;还有“快乐数”的求解过程也涉及到循环检测等内容。此外,《学透哈希表》系列课程则更侧重于指导如何高效利用`Map`接口下的各种方法解决问题,比如`getOrDefault()`等实用功能[^4]。 ```java // 使用HashMap.getOrDefault()获取指定key对应value,如果不存在返回默认值 public int getValueOrDefault(Map<String, Integer> map, String key){ return map.getOrDefault(key, 0); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值