01数组(哈希)笔记

关联容器

map和set在存储数据时,元素的键(key)需要保证不能重复,会对key进行升序排序(从小到大);
multimap和multiset在存储元素时无上述要求。
set中键和值相同(key=value)
在这里插入图片描述
“键值对”并不是普通类型数据,C++ STL 标准库提供了 pair 类模板,其专门用来将 2 个普通元素 first 和 second(可以是 C++ 基本数据类型、结构体、类自定的类型)创建成一个新元素<first, second>。pair 类模板定义在头文件中,所以在使用该类模板之前,需引入此头文件。
创建C++ map容器的几种方法:
1) 通过调用 map 容器类的默认构造函数,可以创建出一个空的 map 容器,比如:

std::map<std::string, int>myMap;

如果程序中已经默认指定了 std 命令空间,这里可以省略 std::。
2)当然在创建 map 容器的同时,也可以进行初始化,比如:

std::map<std::string, int>myMap{ {"C语言教程",10},{"STL教程",20} };

由此,myMap 容器在初始状态下,就包含有 2 个键值对。
再次强调,map 容器中存储的键值对,其本质都是 pair 类模板创建的 pair 对象。因此,下面程序也可以创建出一模一样的 myMap 容器:

std::map<std::string, int>myMap{std::make_pair("C语言教程",10),std::make_pair("STL教程",20)};

除此之外,在某些场景中,可以利用先前已创建好的 map 容器,再创建一个新的 map 容器。例如:

std::map<std::string, int>newMap(myMap);

由此,通过调用 map 容器的拷贝(复制)构造函数,即可成功创建一个和 myMap 完全一样的 newMap 容器。
当然,在以上几种创建 map 容器的基础上,我们都可以手动修改 map 容器的排序规则。默认情况下,map 容器调用 std::less 规则,根据容器内各键值对的键的大小,对所有键值对做升序排序。

http://c.biancheng.net/view/7173.html

#include <iostream>
#include <map>      // map
#include <string>       // string
using namespace std;
int main() {
    //创建空 map 容器,默认根据个键值对中键的值,对键值对做降序排序
    std::map<std::string, std::string, std::greater<std::string>>myMap;
    //调用 emplace() 方法,直接向 myMap 容器中指定位置构造新键值对
    myMap.emplace("C语言教程","http://c.biancheng.net/c/");
    myMap.emplace("Python教程", "http://c.biancheng.net/python/");
    myMap.emplace("STL教程", "http://c.biancheng.net/stl/");
    //输出当前 myMap 容器存储键值对的个数
    cout << "myMap size==" << myMap.size() << endl;
    //判断当前 myMap 容器是否为空
    if (!myMap.empty()) {
        //借助 myMap 容器迭代器,将该容器的键值对逐个输出
        for (auto i = myMap.begin(); i != myMap.end(); ++i) {
            cout << i->first << " " << i->second << endl;
        }
    }  
    return 0;
}

C++ STL map获取键对应值的几种方法:
map 容器中存储的都是 pair 类型的键值对,但几乎在所有使用 map 容器的场景中,经常要做的不是找到指定的 pair 对象(键值对),而是从该容器中找到某个键对应的值。

  1. map 类模板中对[ ]运算符进行了重载,这意味着,类似于借助数组下标可以直接访问数组中元素,通过指定的键,我们可以轻松获取 map 容器中该键对应的值。
#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;
int main() {
    //创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                             {"C语言教程","http://c.biancheng.net/c/"},
                                             {"Java教程","http://c.biancheng.net/java/"} };
    string cValue = myMap["C语言教程"];
    cout << cValue << endl;
    return 0;
}
http://c.biancheng.net/c/

注意,只有当 map 容器中确实存有包含该指定键的键值对,借助重载的 [ ] 运算符才能成功获取该键对应的值;反之,若当前 map 容器中没有包含该指定键的键值对,则此时使用 [ ] 运算符将不再是访问容器中的元素,而变成了向该 map 容器中增添一个键值对。其中,该键值对的键用 [ ] 运算符中指定的键,其对应的值取决于 map 容器规定键值对中值的数据类型,如果是基本数据类型,则值为 0;如果是 string 类型,其值为 “”,即空字符串(即使用该类型的默认值作为键值对的值)。

#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;
int main() {
    //创建空 map 容器
    std::map<std::string, int>myMap;
    int cValue = myMap["C语言教程"];
    for (auto i = myMap.begin(); i != myMap.end(); ++i) {
        cout << i->first << " "<< i->second << endl;
    }
    return 0;
}
C语言教程 0
  1. 除了借助 [ ] 运算符获取 map 容器中指定键对应的值,还可以使用 at() 成员方法。和前一种方法相比,at() 成员方法也需要根据指定的键,才能从容器中找到该键对应的值;不同之处在于,如果在当前容器中查找失败,该方法不会向容器中添加新的键值对,而是直接抛出 out_of_range 异常。
#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;
int main() {
    //创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                             {"C语言教程","http://c.biancheng.net/c/"},
                                             {"Java教程","http://c.biancheng.net/java/"} };
    cout << myMap.at("C语言教程") << endl;
    //下面一行代码会引发 out_of_range 异常
    //cout << myMap.at("Python教程") << endl;
    return 0;
}

3)除了可以直接获取指定键对应的值之外,还可以借助 find() 成员方法间接实现此目的。和以上 2 种方式不同的是,该方法返回的是一个迭代器,即如果查找成功,该迭代器指向查找到的键值对;反之,则指向 map 容器最后一个键值对之后的位置(和 end() 成功方法返回的迭代器一样)。

#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;
int main() {
    //创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                             {"C语言教程","http://c.biancheng.net/c/"},
                                             {"Java教程","http://c.biancheng.net/java/"} };
    map< std::string, std::string >::iterator myIter = myMap.find("C语言教程");
    cout << myIter->first << " " << myIter->second << endl;
    return 0;
}
C语言教程 http://c.biancheng.net/c/

注意,此程序中如果 find() 查找失败,会导致第 13 行代码运行出错。因为当 find() 方法查找失败时,其返回的迭代器指向的是容器中最后一个键值对之后的位置,即不指向任何有意义的键值对,也就没有所谓的 first 和 second 成员了。
。。。
C++ STL map insert()插入数据的4种方式(略)
multimap容器
multimap 容器也用于存储 pair<const K, T> 类型的键值对(其中 K 表示键的类型,T 表示值的类型),其中各个键值对的键的值不能做修改;并且,该容器也会自行根据键的大小对存储的所有键值对做排序操作。和 map 容器的区别在于,multimap 容器中可以同时存储多(≥2)个键相同的键值对。

C++ STL无序关联式容器

无序关联式容器,又称哈希容器。和关联式容器一样,此类容器存储的也是键值对元素;不同之处在于,关联式容器默认情况下会对存储的元素做升序排序,而无序关联式容器不会。
关联式容器的底层实现采用的树存储结构,更确切的说是红黑树结构
无序容器的底层实现采用的是哈希表的存储结构。
unordered_map和unodered_set在存储数据时,元素的键(key)需要保证不能重复,key无序存储;
在这里插入图片描述
实际场景中如果涉及大量遍历容器的操作,建议首选关联式容器;反之,如果更多的操作是通过键获取对应的值,则应首选无序容器

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main()
{
    //创建并初始化一个 unordered_map 容器,其存储的 <string,string> 类型的键值对
    std::unordered_map<std::string, std::string> my_uMap{
        {"C语言教程","http://c.biancheng.net/c/"},
        {"Python教程","http://c.biancheng.net/python/"},
        {"Java教程","http://c.biancheng.net/java/"} };
    //查找指定键对应的值,效率比关联式容器高
    string str = my_uMap.at("C语言教程");
    cout << "str = " << str << endl;
    //使用迭代器遍历哈希容器,效率不如关联式容器
    for (auto iter = my_uMap.begin(); iter != my_uMap.end(); ++iter)
    {
        //pair 类型键值对分为 2 部分
        cout << iter->first << " " << iter->second << endl;
    }
    return 0;
}
程序执行结果为:
str = http://c.biancheng.net/c/
C语言教程 http://c.biancheng.net/c/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/

在这里插入图片描述

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main()
{
    //创建空 umap 容器
    unordered_map<string, string> umap;
    //向 umap 容器添加新键值对
    umap.emplace("Python教程", "http://c.biancheng.net/python/");
    umap.emplace("Java教程", "http://c.biancheng.net/java/");
    umap.emplace("Linux教程", "http://c.biancheng.net/linux/");
    //输出 umap 存储键值对的数量
    cout << "umap size = " << umap.size() << endl;
    //使用迭代器输出 umap 容器存储的所有键值对
    for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
        cout << iter->first << " " << iter->second << endl;
    }
    return 0;
}
程序执行结果为:
umap size = 3
Python教程 http://c.biancheng.net/python/
Linux教程 http://c.biancheng.net/linux/
Java教程 http://c.biancheng.net/java/
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
    //创建 umap 容器
    unordered_map<int, int> umap;
    //向 umap 容器添加 50 个键值对
    for (int i = 1; i <= 50; i++) {
        umap.emplace(i, i);
    }
    //获取键为 49 的键值对所在的范围
    auto pair = umap.equal_range(49);
    //输出 pair 范围内的每个键值对的键的值
    for (auto iter = pair.first; iter != pair.second; ++iter) {
        cout << iter->first <<" ";
    }
    cout << endl;
    //手动调整最大负载因子数
    umap.max_load_factor(3.0);
    //手动调用 rehash() 函数重哈希
    umap.rehash(10);
    //重哈希之后,pair 的范围可能会发生变化
    for (auto iter = pair.first; iter != pair.second; ++iter) {
        cout << iter->first << " ";
    }
    return 0;
}
程序执行结果为:
49
49 17

其它不再过多赘述,多看程序,掌握常用用法

哈希表练习

哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器。
哈希表也有自己的缺点,哈希表是基于数组的,我们知道数组创建后扩容成本比较高,所以当哈希表被填满时,性能下降的比较严重。
哈希表采用的是一种转换思想,其中一个中要的概念是如何将「键」或者「关键字」转换成数组下标?在哈希表中,这个过程有哈希函数来完成,但是并不是每个「键」或者「关键字」都需要通过哈希函数来将其转换成数组下标,有些「键」或者「关键字」可以直接作为数组的下标。
1、给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        map<int,int> a;//建立hash表存放数组元素
        vector<int> b(2,-1);//存放结果
        for(int i=0;i<nums.size();i++)
            a.insert(map<int,int>::value_type(nums[i],i));//建立哈希表
        for(int i=0;i<nums.size();i++)
        {
            if(a.count(target-nums[i])>0&&(a[target-nums[i]]!=i))//在容器中查找以 key 键的键值对的个数。判断是否找到目标元素且目标元素不能是本身
            {
                b[0]=i;
                b[1]=a[target-nums[i]];
                break;
            }
        }
        return b;
    };
};
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int>hashtable;//创建空哈希表
        for(int i=0;i<nums.size();i++){//固定第一个值
            auto it=hashtable.find(target-nums[i]);//在哈希表中查找第二个值,nums[i]数本身为键,i为键值<nums[i],i>;
            //find(key)查找以 key 为键的 键值对,如果找到,则返回一个指向该键值对的正向迭代器;
            //反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。
            if(it!=hashtable.end()){
                return {it->second,i};//哈希表是在访问每一个i之后增加建立的,因此i的值靠后
            }
            hashtable[nums[i]]=i;//将每一个i以及nums[i]添加到哈希表中
        }
        return {};
    }
};

217、给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        sort(nums.begin(), nums.end());//排序
        int n = nums.size();
        for (int i = 0; i < n - 1; i++) {
            if (nums[i] == nums[i + 1]) {
                return true;
            }
        }
        return false;
    }
};

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_map<int,int>hashtable;
        for(int i=0;i<nums.size();i++){
            auto it=hashtable.find(nums[i]);
            if(it!=hashtable.end()){
                return true;
            }
            hashtable[nums[i]]=i;
        }
        return false;
    }
};
class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_set<int>hashtable;
        for(int i=0;i<nums.size();i++){
            auto it=hashtable.find(nums[i]);
            if(it!=hashtable.end()){
                return true;
            }
            hashtable.insert(nums[i]);
        }
        return false;        
    }
};

36、请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

sizeof(array[0][0]):一个元素占用的空间,
sizeof(array[0]):一行元素占用的空间,
sizeof(array):整个数组占用的空间,

行数 = sizeof(array)/sizeof(array[0]);
列数 = sizeof(array[0])/sizeof(array[0][0]);

public:
    bool isValidSudoku(vector<vector<char>>& board) {
        vector<unordered_set<char>> row(9);
        vector<unordered_set<char>> col(9);
        vector<unordered_set<char>> bor(9);
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                if(board[i][j]=='.')continue;
                if(row[i].count(board[i][j])<=0) row[i].insert(board[i][j]);
                else return false;
                if(col[j].count(board[i][j])<=0) col[j].insert(board[i][j]);
                else return false;
                if(bor[(i/3)*3+(j/3)].count(board[i][j])<=0) bor[(i/3)*3+(j/3)].insert(board[i][j]);
                else return false;
            }
        }
        return true;
    }
};

128、给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int n=nums.size();
        int ans=1,t=1;
        if(n==0){
            return 0;
        }
        for(int i=1;i<n;i++){
            if(nums[i-1]==nums[i]-1){
                t++;
                ans<t?ans=t:ans;
            }
            else if(nums[i-1]==nums[i]){
                continue;
            }
            else{
                t=1;
            }           
        }
        return ans;
    }
};
//考虑现将数组排序,然后遍历数组,如果中断则记录对应的长度,每次的长度都要与上一次对比
//注意特殊情况,数组中有相等的元素,数组为空
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> num_set;
        for (const int& num : nums) {//遍历将数组中的元素插入哈希表中
            num_set.insert(num);
        }

        int longestStreak = 0;

        for (const int& num : num_set) {//遍历哈希表
            if (!num_set.count(num - 1)) {//当前元素的前一个元素(num-1)不存在时
                int currentNum = num;
                int currentStreak = 1;

                while (num_set.count(currentNum + 1)) {//当前元素的后一个元素存在
                    currentNum += 1;
                    currentStreak += 1;
                }

                longestStreak = max(longestStreak, currentStreak);
            }
        }

        return longestStreak;           
    }
};

73、给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int m=matrix.size();//行数
        int n=matrix[0].size();//列数
        int flag_row0=false,flag_col0=false;
        for(int i=0;i<m;i++){//验证第一列是否含零,如果含零则标记为置1
            if(!matrix[i][0]){
                flag_col0=true;
            }
        }
        for(int j=0;j<n;j++){//验证第一行是否含零,如果含零则标记为置1
            if(!matrix[0][j]){
                flag_row0=true;
            }
        }
        for(int i=1;i<m;i++){//从第二行、第二列开始遍历矩阵
            for(int j=1;j<n;j++){
                if(!matrix[i][j]){
                    matrix[i][0]=matrix[0][j]=false;//矩阵中存在零,则将对应的第一行和第一列中的元素置零
                }
            }
        }
        for(int i=1;i<m;i++){//从第二行、第二列开始遍历矩阵
            for(int j=1;j<n;j++){
                if(!matrix[i][0]||!matrix[0][j]){//检验矩阵中的第一行和第一列中标记的零位,对应行列全部置零
                    matrix[i][j]=0;
                }
            }
        }
        if(flag_col0){//第一列中如果有零元素,则第一列置零
            for(int i=0;i<m;i++){
                matrix[i][0]=0;
            }
        }
        if(flag_row0){//第一行中如果有零元素,则第一行置零
            for(int j=0;j<n;j++){
                matrix[0][j]=0;
            }
        }
    }
};
//时间复杂度O(mn),空间负载度O(1);
class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int m = matrix.size();//矩阵的行数
        int n = matrix[0].size();//矩阵的列数
        vector<int> row(m,0);//定义含有m个元素的全零数组
        vector<int> col(n,0);//定义含有n个元素的全零数组
        for (int i = 0; i < m; i++) {//遍历矩阵
            for (int j = 0; j < n; j++) {
                if (!matrix[i][j]) {//如果下标为[i][j]的元素为零
                    row[i] = col[j] = true;//则将定义的行和列数组对应位置1,对其进行标记
                }
            }
        }
        for (int i = 0; i < m; i++) {//遍历矩阵
            for (int j = 0; j < n; j++) {
                if (row[i] || col[j]) {//如果是标记过的行列
                    matrix[i][j] = 0;//则将对应元素置0
                }
            }
        }
    }
};
//时间复杂度为O(mn);空间复杂度为O(m+n);

本文仅为本人刷题的部分笔记,如有错误请指出。
本文引用:

https://leetcode-cn.com/
http://c.biancheng.net/view/7166.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值